##// END OF EJS Templates
Added a 'New issue' link in the main menu (accesskey 7)....
Jean-Philippe Lang -
r1067:16e9ffce0d0e
parent child
Show More
@@ -0,0 +1,31
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module Redmine
19 module AccessKeys
20 ACCESSKEYS = {:edit => 'e',
21 :preview => 'r',
22 :quick_search => 'f',
23 :search => '4',
24 :new_issue => '7'
25 }.freeze unless const_defined?(:ACCESSKEYS)
26
27 def self.key_for(action)
28 ACCESSKEYS[action]
29 end
30 end
31 end
@@ -1,295 +1,297
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 IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 layout 'base'
19 layout 'base'
20 menu_item :new_issue, :only => :new
21
20 before_filter :find_issue, :except => [:index, :changes, :preview, :new, :update_form]
22 before_filter :find_issue, :except => [:index, :changes, :preview, :new, :update_form]
21 before_filter :find_project, :only => [:new, :update_form]
23 before_filter :find_project, :only => [:new, :update_form]
22 before_filter :authorize, :except => [:index, :changes, :preview, :update_form]
24 before_filter :authorize, :except => [:index, :changes, :preview, :update_form]
23 before_filter :find_optional_project, :only => [:index, :changes]
25 before_filter :find_optional_project, :only => [:index, :changes]
24 accept_key_auth :index, :changes
26 accept_key_auth :index, :changes
25
27
26 cache_sweeper :issue_sweeper, :only => [ :new, :edit, :update, :destroy ]
28 cache_sweeper :issue_sweeper, :only => [ :new, :edit, :update, :destroy ]
27
29
28 helper :projects
30 helper :projects
29 include ProjectsHelper
31 include ProjectsHelper
30 helper :custom_fields
32 helper :custom_fields
31 include CustomFieldsHelper
33 include CustomFieldsHelper
32 helper :ifpdf
34 helper :ifpdf
33 include IfpdfHelper
35 include IfpdfHelper
34 helper :issue_relations
36 helper :issue_relations
35 include IssueRelationsHelper
37 include IssueRelationsHelper
36 helper :watchers
38 helper :watchers
37 include WatchersHelper
39 include WatchersHelper
38 helper :attachments
40 helper :attachments
39 include AttachmentsHelper
41 include AttachmentsHelper
40 helper :queries
42 helper :queries
41 helper :sort
43 helper :sort
42 include SortHelper
44 include SortHelper
43 include IssuesHelper
45 include IssuesHelper
44
46
45 def index
47 def index
46 sort_init "#{Issue.table_name}.id", "desc"
48 sort_init "#{Issue.table_name}.id", "desc"
47 sort_update
49 sort_update
48 retrieve_query
50 retrieve_query
49 if @query.valid?
51 if @query.valid?
50 limit = %w(pdf csv).include?(params[:format]) ? Setting.issues_export_limit.to_i : per_page_option
52 limit = %w(pdf csv).include?(params[:format]) ? Setting.issues_export_limit.to_i : per_page_option
51 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
53 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
52 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
54 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
53 @issues = Issue.find :all, :order => sort_clause,
55 @issues = Issue.find :all, :order => sort_clause,
54 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
56 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
55 :conditions => @query.statement,
57 :conditions => @query.statement,
56 :limit => limit,
58 :limit => limit,
57 :offset => @issue_pages.current.offset
59 :offset => @issue_pages.current.offset
58 respond_to do |format|
60 respond_to do |format|
59 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
61 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
60 format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
62 format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
61 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
63 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
62 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
64 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
63 end
65 end
64 else
66 else
65 # Send html if the query is not valid
67 # Send html if the query is not valid
66 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
68 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
67 end
69 end
68 end
70 end
69
71
70 def changes
72 def changes
71 sort_init "#{Issue.table_name}.id", "desc"
73 sort_init "#{Issue.table_name}.id", "desc"
72 sort_update
74 sort_update
73 retrieve_query
75 retrieve_query
74 if @query.valid?
76 if @query.valid?
75 @changes = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
77 @changes = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
76 :conditions => @query.statement,
78 :conditions => @query.statement,
77 :limit => 25,
79 :limit => 25,
78 :order => "#{Journal.table_name}.created_on DESC"
80 :order => "#{Journal.table_name}.created_on DESC"
79 end
81 end
80 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
82 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
81 render :layout => false, :content_type => 'application/atom+xml'
83 render :layout => false, :content_type => 'application/atom+xml'
82 end
84 end
83
85
84 def show
86 def show
85 @custom_values = @issue.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
87 @custom_values = @issue.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
86 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
88 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
87 @status_options = @issue.new_statuses_allowed_to(User.current)
89 @status_options = @issue.new_statuses_allowed_to(User.current)
88 @activities = Enumeration::get_values('ACTI')
90 @activities = Enumeration::get_values('ACTI')
89 respond_to do |format|
91 respond_to do |format|
90 format.html { render :template => 'issues/show.rhtml' }
92 format.html { render :template => 'issues/show.rhtml' }
91 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
93 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
92 end
94 end
93 end
95 end
94
96
95 # Add a new issue
97 # Add a new issue
96 # The new issue will be created from an existing one if copy_from parameter is given
98 # The new issue will be created from an existing one if copy_from parameter is given
97 def new
99 def new
98 @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
100 @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
99 @issue.project = @project
101 @issue.project = @project
100 @issue.author = User.current
102 @issue.author = User.current
101 @issue.tracker ||= @project.trackers.find(params[:tracker_id] ? params[:tracker_id] : :first)
103 @issue.tracker ||= @project.trackers.find(params[:tracker_id] ? params[:tracker_id] : :first)
102 if @issue.tracker.nil?
104 if @issue.tracker.nil?
103 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
105 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
104 render :nothing => true, :layout => true
106 render :nothing => true, :layout => true
105 return
107 return
106 end
108 end
107
109
108 default_status = IssueStatus.default
110 default_status = IssueStatus.default
109 unless default_status
111 unless default_status
110 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
112 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
111 render :nothing => true, :layout => true
113 render :nothing => true, :layout => true
112 return
114 return
113 end
115 end
114 @issue.status = default_status
116 @issue.status = default_status
115 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker))
117 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker))
116
118
117 if request.get? || request.xhr?
119 if request.get? || request.xhr?
118 @issue.start_date ||= Date.today
120 @issue.start_date ||= Date.today
119 @custom_values = @issue.custom_values.empty? ?
121 @custom_values = @issue.custom_values.empty? ?
120 @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
122 @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
121 @issue.custom_values
123 @issue.custom_values
122 else
124 else
123 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
125 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
124 # Check that the user is allowed to apply the requested status
126 # Check that the user is allowed to apply the requested status
125 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
127 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
126 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
128 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
127 @issue.custom_values = @custom_values
129 @issue.custom_values = @custom_values
128 if @issue.save
130 if @issue.save
129 attach_files(@issue, params[:attachments])
131 attach_files(@issue, params[:attachments])
130 flash[:notice] = l(:notice_successful_create)
132 flash[:notice] = l(:notice_successful_create)
131 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
133 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
132 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
134 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
133 return
135 return
134 end
136 end
135 end
137 end
136 @priorities = Enumeration::get_values('IPRI')
138 @priorities = Enumeration::get_values('IPRI')
137 render :layout => !request.xhr?
139 render :layout => !request.xhr?
138 end
140 end
139
141
140 def edit
142 def edit
141 @priorities = Enumeration::get_values('IPRI')
143 @priorities = Enumeration::get_values('IPRI')
142 @custom_values = []
144 @custom_values = []
143 if request.get?
145 if request.get?
144 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
146 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
145 else
147 else
146 begin
148 begin
147 journal = @issue.init_journal(User.current)
149 journal = @issue.init_journal(User.current)
148 # Retrieve custom fields and values
150 # Retrieve custom fields and values
149 if params["custom_fields"]
151 if params["custom_fields"]
150 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
152 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
151 @issue.custom_values = @custom_values
153 @issue.custom_values = @custom_values
152 end
154 end
153 @issue.attributes = params[:issue]
155 @issue.attributes = params[:issue]
154 if @issue.save
156 if @issue.save
155 flash[:notice] = l(:notice_successful_update)
157 flash[:notice] = l(:notice_successful_update)
156 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
158 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
157 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
159 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
158 end
160 end
159 rescue ActiveRecord::StaleObjectError
161 rescue ActiveRecord::StaleObjectError
160 # Optimistic locking exception
162 # Optimistic locking exception
161 flash[:error] = l(:notice_locking_conflict)
163 flash[:error] = l(:notice_locking_conflict)
162 end
164 end
163 end
165 end
164 end
166 end
165
167
166 # Attributes that can be updated on workflow transition
168 # Attributes that can be updated on workflow transition
167 # TODO: make it configurable (at least per role)
169 # TODO: make it configurable (at least per role)
168 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
170 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
169
171
170 def update
172 def update
171 @status_options = @issue.new_statuses_allowed_to(User.current)
173 @status_options = @issue.new_statuses_allowed_to(User.current)
172 @activities = Enumeration::get_values('ACTI')
174 @activities = Enumeration::get_values('ACTI')
173 journal = @issue.init_journal(User.current, params[:notes])
175 journal = @issue.init_journal(User.current, params[:notes])
174 # User can change issue attributes only if a workflow transition is allowed
176 # User can change issue attributes only if a workflow transition is allowed
175 if !@status_options.empty? && params[:issue]
177 if !@status_options.empty? && params[:issue]
176 attrs = params[:issue].dup
178 attrs = params[:issue].dup
177 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) }
179 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) }
178 attrs.delete(:status_id) unless @status_options.detect {|s| s.id.to_s == attrs[:status_id].to_s}
180 attrs.delete(:status_id) unless @status_options.detect {|s| s.id.to_s == attrs[:status_id].to_s}
179 @issue.attributes = attrs
181 @issue.attributes = attrs
180 end
182 end
181 if request.post?
183 if request.post?
182 attachments = attach_files(@issue, params[:attachments])
184 attachments = attach_files(@issue, params[:attachments])
183 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
185 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
184 if @issue.save
186 if @issue.save
185 # Log spend time
187 # Log spend time
186 if current_role.allowed_to?(:log_time)
188 if current_role.allowed_to?(:log_time)
187 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
189 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
188 @time_entry.attributes = params[:time_entry]
190 @time_entry.attributes = params[:time_entry]
189 @time_entry.save
191 @time_entry.save
190 end
192 end
191 if !journal.new_record?
193 if !journal.new_record?
192 # Only send notification if something was actually changed
194 # Only send notification if something was actually changed
193 flash[:notice] = l(:notice_successful_update)
195 flash[:notice] = l(:notice_successful_update)
194 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
196 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
195 end
197 end
196 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
198 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
197 end
199 end
198 end
200 end
199 rescue ActiveRecord::StaleObjectError
201 rescue ActiveRecord::StaleObjectError
200 # Optimistic locking exception
202 # Optimistic locking exception
201 flash.now[:error] = l(:notice_locking_conflict)
203 flash.now[:error] = l(:notice_locking_conflict)
202 end
204 end
203
205
204 def destroy
206 def destroy
205 @issue.destroy
207 @issue.destroy
206 redirect_to :action => 'index', :project_id => @project
208 redirect_to :action => 'index', :project_id => @project
207 end
209 end
208
210
209 def destroy_attachment
211 def destroy_attachment
210 a = @issue.attachments.find(params[:attachment_id])
212 a = @issue.attachments.find(params[:attachment_id])
211 a.destroy
213 a.destroy
212 journal = @issue.init_journal(User.current)
214 journal = @issue.init_journal(User.current)
213 journal.details << JournalDetail.new(:property => 'attachment',
215 journal.details << JournalDetail.new(:property => 'attachment',
214 :prop_key => a.id,
216 :prop_key => a.id,
215 :old_value => a.filename)
217 :old_value => a.filename)
216 journal.save
218 journal.save
217 redirect_to :action => 'show', :id => @issue
219 redirect_to :action => 'show', :id => @issue
218 end
220 end
219
221
220 def context_menu
222 def context_menu
221 @priorities = Enumeration.get_values('IPRI').reverse
223 @priorities = Enumeration.get_values('IPRI').reverse
222 @statuses = IssueStatus.find(:all, :order => 'position')
224 @statuses = IssueStatus.find(:all, :order => 'position')
223 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
225 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
224 @assignables = @issue.assignable_users
226 @assignables = @issue.assignable_users
225 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
227 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
226 @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
228 @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
227 :assign => (@allowed_statuses.any? || User.current.allowed_to?(:edit_issues, @project)),
229 :assign => (@allowed_statuses.any? || User.current.allowed_to?(:edit_issues, @project)),
228 :add => User.current.allowed_to?(:add_issues, @project),
230 :add => User.current.allowed_to?(:add_issues, @project),
229 :move => User.current.allowed_to?(:move_issues, @project),
231 :move => User.current.allowed_to?(:move_issues, @project),
230 :copy => (@project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
232 :copy => (@project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
231 :delete => User.current.allowed_to?(:delete_issues, @project)}
233 :delete => User.current.allowed_to?(:delete_issues, @project)}
232 render :layout => false
234 render :layout => false
233 end
235 end
234
236
235 def update_form
237 def update_form
236 @issue = Issue.new(params[:issue])
238 @issue = Issue.new(params[:issue])
237 render :action => :new, :layout => false
239 render :action => :new, :layout => false
238 end
240 end
239
241
240 def preview
242 def preview
241 issue = Issue.find_by_id(params[:id])
243 issue = Issue.find_by_id(params[:id])
242 @attachements = issue.attachments if issue
244 @attachements = issue.attachments if issue
243 @text = params[:issue][:description]
245 @text = params[:issue][:description]
244 render :partial => 'common/preview'
246 render :partial => 'common/preview'
245 end
247 end
246
248
247 private
249 private
248 def find_issue
250 def find_issue
249 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
251 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
250 @project = @issue.project
252 @project = @issue.project
251 rescue ActiveRecord::RecordNotFound
253 rescue ActiveRecord::RecordNotFound
252 render_404
254 render_404
253 end
255 end
254
256
255 def find_project
257 def find_project
256 @project = Project.find(params[:project_id])
258 @project = Project.find(params[:project_id])
257 rescue ActiveRecord::RecordNotFound
259 rescue ActiveRecord::RecordNotFound
258 render_404
260 render_404
259 end
261 end
260
262
261 def find_optional_project
263 def find_optional_project
262 return true unless params[:project_id]
264 return true unless params[:project_id]
263 @project = Project.find(params[:project_id])
265 @project = Project.find(params[:project_id])
264 authorize
266 authorize
265 rescue ActiveRecord::RecordNotFound
267 rescue ActiveRecord::RecordNotFound
266 render_404
268 render_404
267 end
269 end
268
270
269 # Retrieve query from session or build a new query
271 # Retrieve query from session or build a new query
270 def retrieve_query
272 def retrieve_query
271 if params[:query_id]
273 if params[:query_id]
272 @query = Query.find(params[:query_id], :conditions => {:project_id => (@project ? @project.id : nil)})
274 @query = Query.find(params[:query_id], :conditions => {:project_id => (@project ? @project.id : nil)})
273 session[:query] = {:id => @query.id, :project_id => @query.project_id}
275 session[:query] = {:id => @query.id, :project_id => @query.project_id}
274 else
276 else
275 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
277 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
276 # Give it a name, required to be valid
278 # Give it a name, required to be valid
277 @query = Query.new(:name => "_")
279 @query = Query.new(:name => "_")
278 @query.project = @project
280 @query.project = @project
279 if params[:fields] and params[:fields].is_a? Array
281 if params[:fields] and params[:fields].is_a? Array
280 params[:fields].each do |field|
282 params[:fields].each do |field|
281 @query.add_filter(field, params[:operators][field], params[:values][field])
283 @query.add_filter(field, params[:operators][field], params[:values][field])
282 end
284 end
283 else
285 else
284 @query.available_filters.keys.each do |field|
286 @query.available_filters.keys.each do |field|
285 @query.add_short_filter(field, params[field]) if params[field]
287 @query.add_short_filter(field, params[field]) if params[field]
286 end
288 end
287 end
289 end
288 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
290 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
289 else
291 else
290 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
292 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
291 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
293 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
292 end
294 end
293 end
295 end
294 end
296 end
295 end
297 end
@@ -1,450 +1,444
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 def link_to_signin
35 def link_to_signin
36 link_to l(:label_login), { :controller => 'account', :action => 'login' }, :class => 'signin'
36 link_to l(:label_login), { :controller => 'account', :action => 'login' }, :class => 'signin'
37 end
37 end
38
38
39 def link_to_signout
39 def link_to_signout
40 link_to l(:label_logout), { :controller => 'account', :action => 'logout' }, :class => 'logout'
40 link_to l(:label_logout), { :controller => 'account', :action => 'logout' }, :class => 'logout'
41 end
41 end
42
42
43 # Display a link to user's account page
43 # Display a link to user's account page
44 def link_to_user(user)
44 def link_to_user(user)
45 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
45 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
46 end
46 end
47
47
48 def link_to_issue(issue)
48 def link_to_issue(issue)
49 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
49 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
50 end
50 end
51
51
52 def toggle_link(name, id, options={})
52 def toggle_link(name, id, options={})
53 onclick = "Element.toggle('#{id}'); "
53 onclick = "Element.toggle('#{id}'); "
54 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
54 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
55 onclick << "return false;"
55 onclick << "return false;"
56 link_to(name, "#", :onclick => onclick)
56 link_to(name, "#", :onclick => onclick)
57 end
57 end
58
58
59 def show_and_goto_link(name, id, options={})
59 def show_and_goto_link(name, id, options={})
60 onclick = "Element.show('#{id}'); "
60 onclick = "Element.show('#{id}'); "
61 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
61 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
62 onclick << "location.href='##{id}-anchor'; "
62 onclick << "location.href='##{id}-anchor'; "
63 onclick << "return false;"
63 onclick << "return false;"
64 link_to(name, "#", options.merge(:onclick => onclick))
64 link_to(name, "#", options.merge(:onclick => onclick))
65 end
65 end
66
66
67 def image_to_function(name, function, html_options = {})
67 def image_to_function(name, function, html_options = {})
68 html_options.symbolize_keys!
68 html_options.symbolize_keys!
69 tag(:input, html_options.merge({
69 tag(:input, html_options.merge({
70 :type => "image", :src => image_path(name),
70 :type => "image", :src => image_path(name),
71 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
71 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
72 }))
72 }))
73 end
73 end
74
74
75 def prompt_to_remote(name, text, param, url, html_options = {})
75 def prompt_to_remote(name, text, param, url, html_options = {})
76 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
76 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
77 link_to name, {}, html_options
77 link_to name, {}, html_options
78 end
78 end
79
79
80 def format_date(date)
80 def format_date(date)
81 return nil unless date
81 return nil unless date
82 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
82 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
83 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
83 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
84 date.strftime(@date_format)
84 date.strftime(@date_format)
85 end
85 end
86
86
87 def format_time(time, include_date = true)
87 def format_time(time, include_date = true)
88 return nil unless time
88 return nil unless time
89 time = time.to_time if time.is_a?(String)
89 time = time.to_time if time.is_a?(String)
90 zone = User.current.time_zone
90 zone = User.current.time_zone
91 if time.utc?
91 if time.utc?
92 local = zone ? zone.adjust(time) : time.getlocal
92 local = zone ? zone.adjust(time) : time.getlocal
93 else
93 else
94 local = zone ? zone.adjust(time.getutc) : time
94 local = zone ? zone.adjust(time.getutc) : time
95 end
95 end
96 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
96 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
97 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
97 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
98 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
98 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
99 end
99 end
100
100
101 def authoring(created, author)
101 def authoring(created, author)
102 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
102 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
103 l(:label_added_time_by, author || 'Anonymous', time_tag)
103 l(:label_added_time_by, author || 'Anonymous', time_tag)
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", :url => url_param.merge(page_param => paginator.current.previous)},
122 {:update => "content", :url => url_param.merge(page_param => paginator.current.previous)},
123 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
123 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
124
124
125 html << (pagination_links_each(paginator, options) do |n|
125 html << (pagination_links_each(paginator, options) do |n|
126 link_to_remote(n.to_s,
126 link_to_remote(n.to_s,
127 {:url => {:params => url_param.merge(page_param => n)}, :update => 'content'},
127 {:url => {:params => url_param.merge(page_param => n)}, :update => 'content'},
128 {:href => url_for(:params => url_param.merge(page_param => n))})
128 {:href => url_for(:params => url_param.merge(page_param => n))})
129 end || '')
129 end || '')
130
130
131 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
131 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
132 {:update => "content", :url => url_param.merge(page_param => paginator.current.next)},
132 {:update => "content", :url => url_param.merge(page_param => paginator.current.next)},
133 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
133 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
134
134
135 unless count.nil?
135 unless count.nil?
136 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
136 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
137 end
137 end
138
138
139 html
139 html
140 end
140 end
141
141
142 def per_page_links(selected=nil)
142 def per_page_links(selected=nil)
143 url_param = params.dup
143 url_param = params.dup
144 url_param.clear if url_param.has_key?(:set_filter)
144 url_param.clear if url_param.has_key?(:set_filter)
145
145
146 links = Setting.per_page_options_array.collect do |n|
146 links = Setting.per_page_options_array.collect do |n|
147 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
147 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
148 {:href => url_for(url_param.merge(:per_page => n))})
148 {:href => url_for(url_param.merge(:per_page => n))})
149 end
149 end
150 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
150 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
151 end
151 end
152
152
153 def html_title(*args)
153 def html_title(*args)
154 if args.empty?
154 if args.empty?
155 title = []
155 title = []
156 title << @project.name if @project
156 title << @project.name if @project
157 title += @html_title if @html_title
157 title += @html_title if @html_title
158 title << Setting.app_title
158 title << Setting.app_title
159 title.compact.join(' - ')
159 title.compact.join(' - ')
160 else
160 else
161 @html_title ||= []
161 @html_title ||= []
162 @html_title += args
162 @html_title += args
163 end
163 end
164 end
164 end
165
166 ACCESSKEYS = {:edit => 'e',
167 :preview => 'r',
168 :quick_search => 'f',
169 :search => '4',
170 }.freeze unless const_defined?(:ACCESSKEYS)
171
165
172 def accesskey(s)
166 def accesskey(s)
173 ACCESSKEYS[s]
167 Redmine::AccessKeys.key_for s
174 end
168 end
175
169
176 # Formats text according to system settings.
170 # Formats text according to system settings.
177 # 2 ways to call this method:
171 # 2 ways to call this method:
178 # * with a String: textilizable(text, options)
172 # * with a String: textilizable(text, options)
179 # * with an object and one of its attribute: textilizable(issue, :description, options)
173 # * with an object and one of its attribute: textilizable(issue, :description, options)
180 def textilizable(*args)
174 def textilizable(*args)
181 options = args.last.is_a?(Hash) ? args.pop : {}
175 options = args.last.is_a?(Hash) ? args.pop : {}
182 case args.size
176 case args.size
183 when 1
177 when 1
184 obj = nil
178 obj = nil
185 text = args.shift || ''
179 text = args.shift || ''
186 when 2
180 when 2
187 obj = args.shift
181 obj = args.shift
188 text = obj.send(args.shift)
182 text = obj.send(args.shift)
189 else
183 else
190 raise ArgumentError, 'invalid arguments to textilizable'
184 raise ArgumentError, 'invalid arguments to textilizable'
191 end
185 end
192
186
193 # when using an image link, try to use an attachment, if possible
187 # when using an image link, try to use an attachment, if possible
194 attachments = options[:attachments]
188 attachments = options[:attachments]
195 if attachments
189 if attachments
196 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
190 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
197 style = $1
191 style = $1
198 filename = $6
192 filename = $6
199 rf = Regexp.new(filename, Regexp::IGNORECASE)
193 rf = Regexp.new(filename, Regexp::IGNORECASE)
200 # search for the picture in attachments
194 # search for the picture in attachments
201 if found = attachments.detect { |att| att.filename =~ rf }
195 if found = attachments.detect { |att| att.filename =~ rf }
202 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
196 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
203 "!#{style}#{image_url}!"
197 "!#{style}#{image_url}!"
204 else
198 else
205 "!#{style}#{filename}!"
199 "!#{style}#{filename}!"
206 end
200 end
207 end
201 end
208 end
202 end
209
203
210 text = (Setting.text_formatting == 'textile') ?
204 text = (Setting.text_formatting == 'textile') ?
211 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
205 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
212 simple_format(auto_link(h(text)))
206 simple_format(auto_link(h(text)))
213
207
214 # different methods for formatting wiki links
208 # different methods for formatting wiki links
215 case options[:wiki_links]
209 case options[:wiki_links]
216 when :local
210 when :local
217 # used for local links to html files
211 # used for local links to html files
218 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
212 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
219 when :anchor
213 when :anchor
220 # used for single-file wiki export
214 # used for single-file wiki export
221 format_wiki_link = Proc.new {|project, title| "##{title}" }
215 format_wiki_link = Proc.new {|project, title| "##{title}" }
222 else
216 else
223 format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
217 format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
224 end
218 end
225
219
226 project = options[:project] || @project
220 project = options[:project] || @project
227
221
228 # Wiki links
222 # Wiki links
229 #
223 #
230 # Examples:
224 # Examples:
231 # [[mypage]]
225 # [[mypage]]
232 # [[mypage|mytext]]
226 # [[mypage|mytext]]
233 # wiki links can refer other project wikis, using project name or identifier:
227 # wiki links can refer other project wikis, using project name or identifier:
234 # [[project:]] -> wiki starting page
228 # [[project:]] -> wiki starting page
235 # [[project:|mytext]]
229 # [[project:|mytext]]
236 # [[project:mypage]]
230 # [[project:mypage]]
237 # [[project:mypage|mytext]]
231 # [[project:mypage|mytext]]
238 text = text.gsub(/(!)?(\[\[([^\]\|]+)(\|([^\]\|]+))?\]\])/) do |m|
232 text = text.gsub(/(!)?(\[\[([^\]\|]+)(\|([^\]\|]+))?\]\])/) do |m|
239 link_project = project
233 link_project = project
240 esc, all, page, title = $1, $2, $3, $5
234 esc, all, page, title = $1, $2, $3, $5
241 if esc.nil?
235 if esc.nil?
242 if page =~ /^([^\:]+)\:(.*)$/
236 if page =~ /^([^\:]+)\:(.*)$/
243 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
237 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
244 page = $2
238 page = $2
245 title ||= $1 if page.blank?
239 title ||= $1 if page.blank?
246 end
240 end
247
241
248 if link_project && link_project.wiki
242 if link_project && link_project.wiki
249 # check if page exists
243 # check if page exists
250 wiki_page = link_project.wiki.find_page(page)
244 wiki_page = link_project.wiki.find_page(page)
251 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
245 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
252 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
246 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
253 else
247 else
254 # project or wiki doesn't exist
248 # project or wiki doesn't exist
255 title || page
249 title || page
256 end
250 end
257 else
251 else
258 all
252 all
259 end
253 end
260 end
254 end
261
255
262 # Redmine links
256 # Redmine links
263 #
257 #
264 # Examples:
258 # Examples:
265 # Issues:
259 # Issues:
266 # #52 -> Link to issue #52
260 # #52 -> Link to issue #52
267 # Changesets:
261 # Changesets:
268 # r52 -> Link to revision 52
262 # r52 -> Link to revision 52
269 # Documents:
263 # Documents:
270 # document#17 -> Link to document with id 17
264 # document#17 -> Link to document with id 17
271 # document:Greetings -> Link to the document with title "Greetings"
265 # document:Greetings -> Link to the document with title "Greetings"
272 # document:"Some document" -> Link to the document with title "Some document"
266 # document:"Some document" -> Link to the document with title "Some document"
273 # Versions:
267 # Versions:
274 # version#3 -> Link to version with id 3
268 # version#3 -> Link to version with id 3
275 # version:1.0.0 -> Link to version named "1.0.0"
269 # version:1.0.0 -> Link to version named "1.0.0"
276 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
270 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
277 # Attachments:
271 # Attachments:
278 # attachment:file.zip -> Link to the attachment of the current object named file.zip
272 # attachment:file.zip -> Link to the attachment of the current object named file.zip
279 text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
273 text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
280 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
274 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
281 link = nil
275 link = nil
282 if esc.nil?
276 if esc.nil?
283 if prefix.nil? && sep == 'r'
277 if prefix.nil? && sep == 'r'
284 if project && (changeset = project.changesets.find_by_revision(oid))
278 if project && (changeset = project.changesets.find_by_revision(oid))
285 link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
279 link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
286 :title => truncate(changeset.comments, 100))
280 :title => truncate(changeset.comments, 100))
287 end
281 end
288 elsif sep == '#'
282 elsif sep == '#'
289 oid = oid.to_i
283 oid = oid.to_i
290 case prefix
284 case prefix
291 when nil
285 when nil
292 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
286 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
293 link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
287 link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
294 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
288 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
295 link = content_tag('del', link) if issue.closed?
289 link = content_tag('del', link) if issue.closed?
296 end
290 end
297 when 'document'
291 when 'document'
298 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
292 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
299 link = link_to h(document.title), {:controller => 'documents', :action => 'show', :id => document}, :class => 'document'
293 link = link_to h(document.title), {:controller => 'documents', :action => 'show', :id => document}, :class => 'document'
300 end
294 end
301 when 'version'
295 when 'version'
302 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
296 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
303 link = link_to h(version.name), {:controller => 'versions', :action => 'show', :id => version}, :class => 'version'
297 link = link_to h(version.name), {:controller => 'versions', :action => 'show', :id => version}, :class => 'version'
304 end
298 end
305 end
299 end
306 elsif sep == ':'
300 elsif sep == ':'
307 # removes the double quotes if any
301 # removes the double quotes if any
308 name = oid.gsub(%r{^"(.*)"$}, "\\1")
302 name = oid.gsub(%r{^"(.*)"$}, "\\1")
309 case prefix
303 case prefix
310 when 'document'
304 when 'document'
311 if project && document = project.documents.find_by_title(name)
305 if project && document = project.documents.find_by_title(name)
312 link = link_to h(document.title), {:controller => 'documents', :action => 'show', :id => document}, :class => 'document'
306 link = link_to h(document.title), {:controller => 'documents', :action => 'show', :id => document}, :class => 'document'
313 end
307 end
314 when 'version'
308 when 'version'
315 if project && version = project.versions.find_by_name(name)
309 if project && version = project.versions.find_by_name(name)
316 link = link_to h(version.name), {:controller => 'versions', :action => 'show', :id => version}, :class => 'version'
310 link = link_to h(version.name), {:controller => 'versions', :action => 'show', :id => version}, :class => 'version'
317 end
311 end
318 when 'attachment'
312 when 'attachment'
319 if attachments && attachment = attachments.detect {|a| a.filename == name }
313 if attachments && attachment = attachments.detect {|a| a.filename == name }
320 link = link_to h(attachment.filename), {:controller => 'attachments', :action => 'download', :id => attachment}, :class => 'attachment'
314 link = link_to h(attachment.filename), {:controller => 'attachments', :action => 'download', :id => attachment}, :class => 'attachment'
321 end
315 end
322 end
316 end
323 end
317 end
324 end
318 end
325 leading + (link || "#{prefix}#{sep}#{oid}")
319 leading + (link || "#{prefix}#{sep}#{oid}")
326 end
320 end
327
321
328 text
322 text
329 end
323 end
330
324
331 # Same as Rails' simple_format helper without using paragraphs
325 # Same as Rails' simple_format helper without using paragraphs
332 def simple_format_without_paragraph(text)
326 def simple_format_without_paragraph(text)
333 text.to_s.
327 text.to_s.
334 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
328 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
335 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
329 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
336 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
330 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
337 end
331 end
338
332
339 def error_messages_for(object_name, options = {})
333 def error_messages_for(object_name, options = {})
340 options = options.symbolize_keys
334 options = options.symbolize_keys
341 object = instance_variable_get("@#{object_name}")
335 object = instance_variable_get("@#{object_name}")
342 if object && !object.errors.empty?
336 if object && !object.errors.empty?
343 # build full_messages here with controller current language
337 # build full_messages here with controller current language
344 full_messages = []
338 full_messages = []
345 object.errors.each do |attr, msg|
339 object.errors.each do |attr, msg|
346 next if msg.nil?
340 next if msg.nil?
347 msg = msg.first if msg.is_a? Array
341 msg = msg.first if msg.is_a? Array
348 if attr == "base"
342 if attr == "base"
349 full_messages << l(msg)
343 full_messages << l(msg)
350 else
344 else
351 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
345 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
352 end
346 end
353 end
347 end
354 # retrieve custom values error messages
348 # retrieve custom values error messages
355 if object.errors[:custom_values]
349 if object.errors[:custom_values]
356 object.custom_values.each do |v|
350 object.custom_values.each do |v|
357 v.errors.each do |attr, msg|
351 v.errors.each do |attr, msg|
358 next if msg.nil?
352 next if msg.nil?
359 msg = msg.first if msg.is_a? Array
353 msg = msg.first if msg.is_a? Array
360 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
354 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
361 end
355 end
362 end
356 end
363 end
357 end
364 content_tag("div",
358 content_tag("div",
365 content_tag(
359 content_tag(
366 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
360 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
367 ) +
361 ) +
368 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
362 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
369 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
363 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
370 )
364 )
371 else
365 else
372 ""
366 ""
373 end
367 end
374 end
368 end
375
369
376 def lang_options_for_select(blank=true)
370 def lang_options_for_select(blank=true)
377 (blank ? [["(auto)", ""]] : []) +
371 (blank ? [["(auto)", ""]] : []) +
378 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
372 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
379 end
373 end
380
374
381 def label_tag_for(name, option_tags = nil, options = {})
375 def label_tag_for(name, option_tags = nil, options = {})
382 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
376 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
383 content_tag("label", label_text)
377 content_tag("label", label_text)
384 end
378 end
385
379
386 def labelled_tabular_form_for(name, object, options, &proc)
380 def labelled_tabular_form_for(name, object, options, &proc)
387 options[:html] ||= {}
381 options[:html] ||= {}
388 options[:html].store :class, "tabular"
382 options[:html].store :class, "tabular"
389 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
383 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
390 end
384 end
391
385
392 def check_all_links(form_name)
386 def check_all_links(form_name)
393 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
387 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
394 " | " +
388 " | " +
395 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
389 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
396 end
390 end
397
391
398 def progress_bar(pcts, options={})
392 def progress_bar(pcts, options={})
399 pcts = [pcts, pcts] unless pcts.is_a?(Array)
393 pcts = [pcts, pcts] unless pcts.is_a?(Array)
400 pcts[1] = pcts[1] - pcts[0]
394 pcts[1] = pcts[1] - pcts[0]
401 pcts << (100 - pcts[1] - pcts[0])
395 pcts << (100 - pcts[1] - pcts[0])
402 width = options[:width] || '100px;'
396 width = options[:width] || '100px;'
403 legend = options[:legend] || ''
397 legend = options[:legend] || ''
404 content_tag('table',
398 content_tag('table',
405 content_tag('tr',
399 content_tag('tr',
406 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
400 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
407 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
401 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
408 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
402 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
409 ), :class => 'progress', :style => "width: #{width};") +
403 ), :class => 'progress', :style => "width: #{width};") +
410 content_tag('p', legend, :class => 'pourcent')
404 content_tag('p', legend, :class => 'pourcent')
411 end
405 end
412
406
413 def context_menu_link(name, url, options={})
407 def context_menu_link(name, url, options={})
414 options[:class] ||= ''
408 options[:class] ||= ''
415 if options.delete(:selected)
409 if options.delete(:selected)
416 options[:class] << ' icon-checked disabled'
410 options[:class] << ' icon-checked disabled'
417 options[:disabled] = true
411 options[:disabled] = true
418 end
412 end
419 if options.delete(:disabled)
413 if options.delete(:disabled)
420 options.delete(:method)
414 options.delete(:method)
421 options.delete(:confirm)
415 options.delete(:confirm)
422 options.delete(:onclick)
416 options.delete(:onclick)
423 options[:class] << ' disabled'
417 options[:class] << ' disabled'
424 url = '#'
418 url = '#'
425 end
419 end
426 link_to name, url, options
420 link_to name, url, options
427 end
421 end
428
422
429 def calendar_for(field_id)
423 def calendar_for(field_id)
430 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
424 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
431 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
425 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
432 end
426 end
433
427
434 def wikitoolbar_for(field_id)
428 def wikitoolbar_for(field_id)
435 return '' unless Setting.text_formatting == 'textile'
429 return '' unless Setting.text_formatting == 'textile'
436 javascript_include_tag('jstoolbar/jstoolbar') +
430 javascript_include_tag('jstoolbar/jstoolbar') +
437 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
431 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
438 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
432 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
439 end
433 end
440
434
441 def content_for(name, content = nil, &block)
435 def content_for(name, content = nil, &block)
442 @has_content ||= {}
436 @has_content ||= {}
443 @has_content[name] = true
437 @has_content[name] = true
444 super(name, content, &block)
438 super(name, content, &block)
445 end
439 end
446
440
447 def has_content?(name)
441 def has_content?(name)
448 (@has_content && @has_content[name]) || false
442 (@has_content && @has_content[name]) || false
449 end
443 end
450 end
444 end
@@ -1,199 +1,191
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 module ProjectsHelper
18 module ProjectsHelper
19 def link_to_version(version, options = {})
19 def link_to_version(version, options = {})
20 return '' unless version && version.is_a?(Version)
20 return '' unless version && version.is_a?(Version)
21 link_to version.name, {:controller => 'projects',
21 link_to version.name, {:controller => 'projects',
22 :action => 'roadmap',
22 :action => 'roadmap',
23 :id => version.project_id,
23 :id => version.project_id,
24 :completed => (version.completed? ? 1 : nil),
24 :completed => (version.completed? ? 1 : nil),
25 :anchor => version.name
25 :anchor => version.name
26 }, options
26 }, options
27 end
27 end
28
28
29 def project_settings_tabs
29 def project_settings_tabs
30 tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
30 tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
31 {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
31 {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
32 {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
32 {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
33 {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
33 {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
34 {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
34 {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
35 {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
35 {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
36 {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
36 {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
37 {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}
37 {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}
38 ]
38 ]
39 tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
39 tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
40 end
40 end
41
41
42 # Generates a gantt image
42 # Generates a gantt image
43 # Only defined if RMagick is avalaible
43 # Only defined if RMagick is avalaible
44 def gantt_image(events, date_from, months, zoom)
44 def gantt_image(events, date_from, months, zoom)
45 date_to = (date_from >> months)-1
45 date_to = (date_from >> months)-1
46 show_weeks = zoom > 1
46 show_weeks = zoom > 1
47 show_days = zoom > 2
47 show_days = zoom > 2
48
48
49 subject_width = 320
49 subject_width = 320
50 header_heigth = 18
50 header_heigth = 18
51 # width of one day in pixels
51 # width of one day in pixels
52 zoom = zoom*2
52 zoom = zoom*2
53 g_width = (date_to - date_from + 1)*zoom
53 g_width = (date_to - date_from + 1)*zoom
54 g_height = 20 * events.length + 20
54 g_height = 20 * events.length + 20
55 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
55 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
56 height = g_height + headers_heigth
56 height = g_height + headers_heigth
57
57
58 imgl = Magick::ImageList.new
58 imgl = Magick::ImageList.new
59 imgl.new_image(subject_width+g_width+1, height)
59 imgl.new_image(subject_width+g_width+1, height)
60 gc = Magick::Draw.new
60 gc = Magick::Draw.new
61
61
62 # Subjects
62 # Subjects
63 top = headers_heigth + 20
63 top = headers_heigth + 20
64 gc.fill('black')
64 gc.fill('black')
65 gc.stroke('transparent')
65 gc.stroke('transparent')
66 gc.stroke_width(1)
66 gc.stroke_width(1)
67 events.each do |i|
67 events.each do |i|
68 gc.text(4, top + 2, (i.is_a?(Issue) ? i.subject : i.name))
68 gc.text(4, top + 2, (i.is_a?(Issue) ? i.subject : i.name))
69 top = top + 20
69 top = top + 20
70 end
70 end
71
71
72 # Months headers
72 # Months headers
73 month_f = date_from
73 month_f = date_from
74 left = subject_width
74 left = subject_width
75 months.times do
75 months.times do
76 width = ((month_f >> 1) - month_f) * zoom
76 width = ((month_f >> 1) - month_f) * zoom
77 gc.fill('white')
77 gc.fill('white')
78 gc.stroke('grey')
78 gc.stroke('grey')
79 gc.stroke_width(1)
79 gc.stroke_width(1)
80 gc.rectangle(left, 0, left + width, height)
80 gc.rectangle(left, 0, left + width, height)
81 gc.fill('black')
81 gc.fill('black')
82 gc.stroke('transparent')
82 gc.stroke('transparent')
83 gc.stroke_width(1)
83 gc.stroke_width(1)
84 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
84 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
85 left = left + width
85 left = left + width
86 month_f = month_f >> 1
86 month_f = month_f >> 1
87 end
87 end
88
88
89 # Weeks headers
89 # Weeks headers
90 if show_weeks
90 if show_weeks
91 left = subject_width
91 left = subject_width
92 height = header_heigth
92 height = header_heigth
93 if date_from.cwday == 1
93 if date_from.cwday == 1
94 # date_from is monday
94 # date_from is monday
95 week_f = date_from
95 week_f = date_from
96 else
96 else
97 # find next monday after date_from
97 # find next monday after date_from
98 week_f = date_from + (7 - date_from.cwday + 1)
98 week_f = date_from + (7 - date_from.cwday + 1)
99 width = (7 - date_from.cwday + 1) * zoom
99 width = (7 - date_from.cwday + 1) * zoom
100 gc.fill('white')
100 gc.fill('white')
101 gc.stroke('grey')
101 gc.stroke('grey')
102 gc.stroke_width(1)
102 gc.stroke_width(1)
103 gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
103 gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
104 left = left + width
104 left = left + width
105 end
105 end
106 while week_f <= date_to
106 while week_f <= date_to
107 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
107 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
108 gc.fill('white')
108 gc.fill('white')
109 gc.stroke('grey')
109 gc.stroke('grey')
110 gc.stroke_width(1)
110 gc.stroke_width(1)
111 gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
111 gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
112 gc.fill('black')
112 gc.fill('black')
113 gc.stroke('transparent')
113 gc.stroke('transparent')
114 gc.stroke_width(1)
114 gc.stroke_width(1)
115 gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
115 gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
116 left = left + width
116 left = left + width
117 week_f = week_f+7
117 week_f = week_f+7
118 end
118 end
119 end
119 end
120
120
121 # Days details (week-end in grey)
121 # Days details (week-end in grey)
122 if show_days
122 if show_days
123 left = subject_width
123 left = subject_width
124 height = g_height + header_heigth - 1
124 height = g_height + header_heigth - 1
125 wday = date_from.cwday
125 wday = date_from.cwday
126 (date_to - date_from + 1).to_i.times do
126 (date_to - date_from + 1).to_i.times do
127 width = zoom
127 width = zoom
128 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
128 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
129 gc.stroke('grey')
129 gc.stroke('grey')
130 gc.stroke_width(1)
130 gc.stroke_width(1)
131 gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
131 gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
132 left = left + width
132 left = left + width
133 wday = wday + 1
133 wday = wday + 1
134 wday = 1 if wday > 7
134 wday = 1 if wday > 7
135 end
135 end
136 end
136 end
137
137
138 # border
138 # border
139 gc.fill('transparent')
139 gc.fill('transparent')
140 gc.stroke('grey')
140 gc.stroke('grey')
141 gc.stroke_width(1)
141 gc.stroke_width(1)
142 gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
142 gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
143 gc.stroke('black')
143 gc.stroke('black')
144 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
144 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
145
145
146 # content
146 # content
147 top = headers_heigth + 20
147 top = headers_heigth + 20
148 gc.stroke('transparent')
148 gc.stroke('transparent')
149 events.each do |i|
149 events.each do |i|
150 if i.is_a?(Issue)
150 if i.is_a?(Issue)
151 i_start_date = (i.start_date >= date_from ? i.start_date : date_from )
151 i_start_date = (i.start_date >= date_from ? i.start_date : date_from )
152 i_end_date = (i.due_date <= date_to ? i.due_date : date_to )
152 i_end_date = (i.due_date <= date_to ? i.due_date : date_to )
153 i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor
153 i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor
154 i_done_date = (i_done_date <= date_from ? date_from : i_done_date )
154 i_done_date = (i_done_date <= date_from ? date_from : i_done_date )
155 i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
155 i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
156 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
156 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
157
157
158 i_left = subject_width + ((i_start_date - date_from)*zoom).floor
158 i_left = subject_width + ((i_start_date - date_from)*zoom).floor
159 i_width = ((i_end_date - i_start_date + 1)*zoom).floor # total width of the issue
159 i_width = ((i_end_date - i_start_date + 1)*zoom).floor # total width of the issue
160 d_width = ((i_done_date - i_start_date)*zoom).floor # done width
160 d_width = ((i_done_date - i_start_date)*zoom).floor # done width
161 l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor : 0 # delay width
161 l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor : 0 # delay width
162
162
163 gc.fill('grey')
163 gc.fill('grey')
164 gc.rectangle(i_left, top, i_left + i_width, top - 6)
164 gc.rectangle(i_left, top, i_left + i_width, top - 6)
165 gc.fill('red')
165 gc.fill('red')
166 gc.rectangle(i_left, top, i_left + l_width, top - 6) if l_width > 0
166 gc.rectangle(i_left, top, i_left + l_width, top - 6) if l_width > 0
167 gc.fill('blue')
167 gc.fill('blue')
168 gc.rectangle(i_left, top, i_left + d_width, top - 6) if d_width > 0
168 gc.rectangle(i_left, top, i_left + d_width, top - 6) if d_width > 0
169 gc.fill('black')
169 gc.fill('black')
170 gc.text(i_left + i_width + 5,top + 1, "#{i.status.name} #{i.done_ratio}%")
170 gc.text(i_left + i_width + 5,top + 1, "#{i.status.name} #{i.done_ratio}%")
171 else
171 else
172 i_left = subject_width + ((i.start_date - date_from)*zoom).floor
172 i_left = subject_width + ((i.start_date - date_from)*zoom).floor
173 gc.fill('green')
173 gc.fill('green')
174 gc.rectangle(i_left, top, i_left + 6, top - 6)
174 gc.rectangle(i_left, top, i_left + 6, top - 6)
175 gc.fill('black')
175 gc.fill('black')
176 gc.text(i_left + 11, top + 1, i.name)
176 gc.text(i_left + 11, top + 1, i.name)
177 end
177 end
178 top = top + 20
178 top = top + 20
179 end
179 end
180
180
181 # today red line
181 # today red line
182 if Date.today >= date_from and Date.today <= date_to
182 if Date.today >= date_from and Date.today <= date_to
183 gc.stroke('red')
183 gc.stroke('red')
184 x = (Date.today-date_from+1)*zoom + subject_width
184 x = (Date.today-date_from+1)*zoom + subject_width
185 gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
185 gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
186 end
186 end
187
187
188 gc.draw(imgl)
188 gc.draw(imgl)
189 imgl
189 imgl
190 end if Object.const_defined?(:Magick)
190 end if Object.const_defined?(:Magick)
191
192 def new_issue_selector
193 trackers = @project.trackers
194 # can't use form tag inside helper
195 content_tag('form',
196 select_tag('tracker_id', '<option></option>' + options_from_collection_for_select(trackers, 'id', 'name'), :onchange => "if (this.value != '') {this.form.submit()}"),
197 :action => url_for(:controller => 'issues', :action => 'new', :project_id => @project), :method => 'get')
198 end
199 end
191 end
@@ -1,18 +1,13
1 <% if authorize_for('issues', 'new') && @project.trackers.any? %>
2 <h3><%= l(:label_issue_new) %></h3>
3 <%= l(:label_tracker) %>: <%= new_issue_selector %>
4 <% end %>
5
6 <h3><%= l(:label_issue_plural) %></h3>
1 <h3><%= l(:label_issue_plural) %></h3>
7 <%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %><br />
2 <%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %><br />
8 <%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br />
3 <%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br />
9 <%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %>
4 <%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %>
10
5
11 <h3><%= l(:label_query_plural) %></h3>
6 <h3><%= l(:label_query_plural) %></h3>
12
7
13 <% queries = @project.queries.find(:all,
8 <% queries = @project.queries.find(:all,
14 :order => "name ASC",
9 :order => "name ASC",
15 :conditions => ["is_public = ? or user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
10 :conditions => ["is_public = ? or user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
16 queries.each do |query| %>
11 queries.each do |query| %>
17 <%= link_to query.name, :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %><br />
12 <%= link_to query.name, :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %><br />
18 <% end %>
13 <% end %>
@@ -1,85 +1,80
1 <h2><%=l(:label_overview)%></h2>
1 <h2><%=l(:label_overview)%></h2>
2
2
3 <div class="splitcontentleft">
3 <div class="splitcontentleft">
4 <%= textilizable @project.description %>
4 <%= textilizable @project.description %>
5 <ul>
5 <ul>
6 <% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= auto_link @project.homepage %></li><% end %>
6 <% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= auto_link @project.homepage %></li><% end %>
7 <% if @subprojects.any? %>
7 <% if @subprojects.any? %>
8 <li><%=l(:label_subproject_plural)%>: <%= @subprojects.collect{|p| link_to(h(p.name), :action => 'show', :id => p)}.join(", ") %></li>
8 <li><%=l(:label_subproject_plural)%>: <%= @subprojects.collect{|p| link_to(h(p.name), :action => 'show', :id => p)}.join(", ") %></li>
9 <% end %>
9 <% end %>
10 <% if @project.parent %>
10 <% if @project.parent %>
11 <li><%=l(:field_parent)%>: <%= link_to h(@project.parent.name), :controller => 'projects', :action => 'show', :id => @project.parent %></li>
11 <li><%=l(:field_parent)%>: <%= link_to h(@project.parent.name), :controller => 'projects', :action => 'show', :id => @project.parent %></li>
12 <% end %>
12 <% end %>
13 <% for custom_value in @custom_values %>
13 <% for custom_value in @custom_values %>
14 <% if !custom_value.value.empty? %>
14 <% if !custom_value.value.empty? %>
15 <li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
15 <li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
16 <% end %>
16 <% end %>
17 <% end %>
17 <% end %>
18 </ul>
18 </ul>
19
19
20 <% if User.current.allowed_to?(:view_issues, @project) %>
20 <% if User.current.allowed_to?(:view_issues, @project) %>
21 <div class="box">
21 <div class="box">
22 <h3 class="icon22 icon22-tracker"><%=l(:label_issue_tracking)%></h3>
22 <h3 class="icon22 icon22-tracker"><%=l(:label_issue_tracking)%></h3>
23 <ul>
23 <ul>
24 <% for tracker in @trackers %>
24 <% for tracker in @trackers %>
25 <li><%= link_to tracker.name, :controller => 'issues', :action => 'index', :project_id => @project,
25 <li><%= link_to tracker.name, :controller => 'issues', :action => 'index', :project_id => @project,
26 :set_filter => 1,
26 :set_filter => 1,
27 "tracker_id" => tracker.id %>:
27 "tracker_id" => tracker.id %>:
28 <%= @open_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_open_issues, @open_issues_by_tracker[tracker] || 0) %>
28 <%= @open_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_open_issues, @open_issues_by_tracker[tracker] || 0) %>
29 <%= l(:label_on) %> <%= @total_issues_by_tracker[tracker] || 0 %></li>
29 <%= l(:label_on) %> <%= @total_issues_by_tracker[tracker] || 0 %></li>
30 <% end %>
30 <% end %>
31 </ul>
31 </ul>
32 <p><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 %></p>
32 <p><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 %></p>
33 </div>
33 </div>
34 <% end %>
34 <% end %>
35 </div>
35 </div>
36
36
37 <div class="splitcontentright">
37 <div class="splitcontentright">
38 <% if @members_by_role.any? %>
38 <% if @members_by_role.any? %>
39 <div class="box">
39 <div class="box">
40 <h3 class="icon22 icon22-users"><%=l(:label_member_plural)%></h3>
40 <h3 class="icon22 icon22-users"><%=l(:label_member_plural)%></h3>
41 <p><% @members_by_role.keys.sort.each do |role| %>
41 <p><% @members_by_role.keys.sort.each do |role| %>
42 <%= role.name %>:
42 <%= role.name %>:
43 <%= @members_by_role[role].collect(&:user).sort.collect{|u| link_to_user u}.join(", ") %>
43 <%= @members_by_role[role].collect(&:user).sort.collect{|u| link_to_user u}.join(", ") %>
44 <br />
44 <br />
45 <% end %></p>
45 <% end %></p>
46 </div>
46 </div>
47 <% end %>
47 <% end %>
48
48
49 <% if @news.any? && authorize_for('news', 'index') %>
49 <% if @news.any? && authorize_for('news', 'index') %>
50 <div class="box">
50 <div class="box">
51 <h3><%=l(:label_news_latest)%></h3>
51 <h3><%=l(:label_news_latest)%></h3>
52 <%= render :partial => 'news/news', :collection => @news %>
52 <%= render :partial => 'news/news', :collection => @news %>
53 <p><%= link_to l(:label_news_view_all), :controller => 'news', :action => 'index', :project_id => @project %></p>
53 <p><%= link_to l(:label_news_view_all), :controller => 'news', :action => 'index', :project_id => @project %></p>
54 </div>
54 </div>
55 <% end %>
55 <% end %>
56 </div>
56 </div>
57
57
58 <% content_for :sidebar do %>
58 <% content_for :sidebar do %>
59 <% if authorize_for('issues', 'new') && @project.trackers.any? %>
60 <h3><%= l(:label_issue_new) %></h3>
61 <%= l(:label_tracker) %>: <%= new_issue_selector %>
62 <% end %>
63
64 <% planning_links = []
59 <% planning_links = []
65 planning_links << link_to_if_authorized(l(:label_calendar), :action => 'calendar', :id => @project)
60 planning_links << link_to_if_authorized(l(:label_calendar), :action => 'calendar', :id => @project)
66 planning_links << link_to_if_authorized(l(:label_gantt), :action => 'gantt', :id => @project)
61 planning_links << link_to_if_authorized(l(:label_gantt), :action => 'gantt', :id => @project)
67 planning_links.compact!
62 planning_links.compact!
68 unless planning_links.empty? %>
63 unless planning_links.empty? %>
69 <h3>Planning</h3>
64 <h3>Planning</h3>
70 <p><%= planning_links.join(' | ') %></p>
65 <p><%= planning_links.join(' | ') %></p>
71 <% end %>
66 <% end %>
72
67
73 <% if @total_hours && User.current.allowed_to?(:view_time_entries, @project) %>
68 <% if @total_hours && User.current.allowed_to?(:view_time_entries, @project) %>
74 <h3><%= l(:label_spent_time) %></h3>
69 <h3><%= l(:label_spent_time) %></h3>
75 <p><span class="icon icon-time"><%= lwr(:label_f_hour, @total_hours) %></span></p>
70 <p><span class="icon icon-time"><%= lwr(:label_f_hour, @total_hours) %></span></p>
76 <p><%= link_to(l(:label_details), {:controller => 'timelog', :action => 'details', :project_id => @project}) %> |
71 <p><%= link_to(l(:label_details), {:controller => 'timelog', :action => 'details', :project_id => @project}) %> |
77 <%= link_to(l(:label_report), {:controller => 'timelog', :action => 'report', :project_id => @project}) %></p>
72 <%= link_to(l(:label_report), {:controller => 'timelog', :action => 'report', :project_id => @project}) %></p>
78 <% end %>
73 <% end %>
79 <% end %>
74 <% end %>
80
75
81 <% content_for :header_tags do %>
76 <% content_for :header_tags do %>
82 <%= auto_discovery_link_tag(:atom, {:action => 'activity', :id => @project, :format => 'atom', :key => User.current.rss_key}) %>
77 <%= auto_discovery_link_tag(:atom, {:action => 'activity', :id => @project, :format => 'atom', :key => User.current.rss_key}) %>
83 <% end %>
78 <% end %>
84
79
85 <% html_title(l(:label_overview)) -%>
80 <% html_title(l(:label_overview)) -%>
@@ -1,108 +1,110
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/mime_type'
3 require 'redmine/mime_type'
4 require 'redmine/themes'
4 require 'redmine/themes'
5 require 'redmine/plugin'
5 require 'redmine/plugin'
6
6
7 begin
7 begin
8 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
8 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
9 rescue LoadError
9 rescue LoadError
10 # RMagick is not available
10 # RMagick is not available
11 end
11 end
12
12
13 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar )
13 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar )
14
14
15 # Permissions
15 # Permissions
16 Redmine::AccessControl.map do |map|
16 Redmine::AccessControl.map do |map|
17 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
17 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
18 map.permission :search_project, {:search => :index}, :public => true
18 map.permission :search_project, {:search => :index}, :public => true
19 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
19 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
20 map.permission :select_project_modules, {:projects => :modules}, :require => :member
20 map.permission :select_project_modules, {:projects => :modules}, :require => :member
21 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
21 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
22 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
22 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
23
23
24 map.project_module :issue_tracking do |map|
24 map.project_module :issue_tracking do |map|
25 # Issue categories
25 # Issue categories
26 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
26 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
27 # Issues
27 # Issues
28 map.permission :view_issues, {:projects => [:changelog, :roadmap],
28 map.permission :view_issues, {:projects => [:changelog, :roadmap],
29 :issues => [:index, :changes, :show, :context_menu],
29 :issues => [:index, :changes, :show, :context_menu],
30 :versions => [:show, :status_by],
30 :versions => [:show, :status_by],
31 :queries => :index,
31 :queries => :index,
32 :reports => :issue_report}, :public => true
32 :reports => :issue_report}, :public => true
33 map.permission :add_issues, {:issues => :new}
33 map.permission :add_issues, {:issues => :new}
34 map.permission :edit_issues, {:projects => :bulk_edit_issues,
34 map.permission :edit_issues, {:projects => :bulk_edit_issues,
35 :issues => [:edit, :update, :destroy_attachment]}
35 :issues => [:edit, :update, :destroy_attachment]}
36 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
36 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
37 map.permission :add_issue_notes, {:issues => :update}
37 map.permission :add_issue_notes, {:issues => :update}
38 map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
38 map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
39 map.permission :delete_issues, {:issues => :destroy}, :require => :member
39 map.permission :delete_issues, {:issues => :destroy}, :require => :member
40 # Queries
40 # Queries
41 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
41 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
42 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
42 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
43 # Gantt & calendar
43 # Gantt & calendar
44 map.permission :view_gantt, :projects => :gantt
44 map.permission :view_gantt, :projects => :gantt
45 map.permission :view_calendar, :projects => :calendar
45 map.permission :view_calendar, :projects => :calendar
46 end
46 end
47
47
48 map.project_module :time_tracking do |map|
48 map.project_module :time_tracking do |map|
49 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
49 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
50 map.permission :view_time_entries, :timelog => [:details, :report]
50 map.permission :view_time_entries, :timelog => [:details, :report]
51 end
51 end
52
52
53 map.project_module :news do |map|
53 map.project_module :news do |map|
54 map.permission :manage_news, {:projects => :add_news, :news => [:edit, :destroy, :destroy_comment]}, :require => :member
54 map.permission :manage_news, {:projects => :add_news, :news => [:edit, :destroy, :destroy_comment]}, :require => :member
55 map.permission :view_news, {:news => [:index, :show]}, :public => true
55 map.permission :view_news, {:news => [:index, :show]}, :public => true
56 map.permission :comment_news, {:news => :add_comment}
56 map.permission :comment_news, {:news => :add_comment}
57 end
57 end
58
58
59 map.project_module :documents do |map|
59 map.project_module :documents do |map|
60 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
60 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
61 map.permission :view_documents, :documents => [:index, :show, :download]
61 map.permission :view_documents, :documents => [:index, :show, :download]
62 end
62 end
63
63
64 map.project_module :files do |map|
64 map.project_module :files do |map|
65 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
65 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
66 map.permission :view_files, :projects => :list_files, :versions => :download
66 map.permission :view_files, :projects => :list_files, :versions => :download
67 end
67 end
68
68
69 map.project_module :wiki do |map|
69 map.project_module :wiki do |map|
70 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
70 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
71 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
71 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
72 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
72 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
73 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special]
73 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special]
74 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
74 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
75 end
75 end
76
76
77 map.project_module :repository do |map|
77 map.project_module :repository do |map|
78 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
78 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
79 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
79 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
80 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
80 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
81 end
81 end
82
82
83 map.project_module :boards do |map|
83 map.project_module :boards do |map|
84 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
84 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
85 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
85 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
86 map.permission :add_messages, {:messages => [:new, :reply]}
86 map.permission :add_messages, {:messages => [:new, :reply]}
87 map.permission :edit_messages, {:messages => :edit}, :require => :member
87 map.permission :edit_messages, {:messages => :edit}, :require => :member
88 map.permission :delete_messages, {:messages => :destroy}, :require => :member
88 map.permission :delete_messages, {:messages => :destroy}, :require => :member
89 end
89 end
90 end
90 end
91
91
92 # Project menu configuration
92 # Project menu configuration
93 Redmine::MenuManager.map :project_menu do |menu|
93 Redmine::MenuManager.map :project_menu do |menu|
94 menu.push :overview, { :controller => 'projects', :action => 'show' }, :caption => :label_overview
94 menu.push :overview, { :controller => 'projects', :action => 'show' }, :caption => :label_overview
95 menu.push :activity, { :controller => 'projects', :action => 'activity' }, :caption => :label_activity
95 menu.push :activity, { :controller => 'projects', :action => 'activity' }, :caption => :label_activity
96 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' }, :caption => :label_roadmap
96 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' }, :caption => :label_roadmap
97 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
97 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
98 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
99 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
98 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
100 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
99 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
101 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
100 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
102 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
101 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }, :caption => :label_wiki
103 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }, :caption => :label_wiki
102 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
104 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
103 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
105 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
104 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
106 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
105 menu.push :repository, { :controller => 'repositories', :action => 'show' },
107 menu.push :repository, { :controller => 'repositories', :action => 'show' },
106 :if => Proc.new { |p| p.repository && !p.repository.new_record? }, :caption => :label_repository
108 :if => Proc.new { |p| p.repository && !p.repository.new_record? }, :caption => :label_repository
107 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :caption => :label_settings
109 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :caption => :label_settings
108 end
110 end
@@ -1,119 +1,125
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 Redmine
18 module Redmine
19 module MenuManager
19 module MenuManager
20 module MenuController
20 module MenuController
21 def self.included(base)
21 def self.included(base)
22 base.extend(ClassMethods)
22 base.extend(ClassMethods)
23 end
23 end
24
24
25 module ClassMethods
25 module ClassMethods
26 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
26 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
27 mattr_accessor :menu_items
27 mattr_accessor :menu_items
28
28
29 # Set the menu item name for a controller or specific actions
29 # Set the menu item name for a controller or specific actions
30 # Examples:
30 # Examples:
31 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
31 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
32 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
32 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
33 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
33 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
34 #
34 #
35 # The default menu item name for a controller is controller_name by default
35 # The default menu item name for a controller is controller_name by default
36 # Eg. the default menu item name for ProjectsController is :projects
36 # Eg. the default menu item name for ProjectsController is :projects
37 def menu_item(id, options = {})
37 def menu_item(id, options = {})
38 if actions = options[:only]
38 if actions = options[:only]
39 actions = [] << actions unless actions.is_a?(Array)
39 actions = [] << actions unless actions.is_a?(Array)
40 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
40 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
41 else
41 else
42 menu_items[controller_name.to_sym][:default] = id
42 menu_items[controller_name.to_sym][:default] = id
43 end
43 end
44 end
44 end
45 end
45 end
46
46
47 def menu_items
47 def menu_items
48 self.class.menu_items
48 self.class.menu_items
49 end
49 end
50
50
51 # Returns the menu item name according to the current action
51 # Returns the menu item name according to the current action
52 def current_menu_item
52 def current_menu_item
53 menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
53 menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
54 menu_items[controller_name.to_sym][:default]
54 menu_items[controller_name.to_sym][:default]
55 end
55 end
56 end
56 end
57
57
58 module MenuHelper
58 module MenuHelper
59 # Returns the current menu item name
59 # Returns the current menu item name
60 def current_menu_item
60 def current_menu_item
61 @controller.current_menu_item
61 @controller.current_menu_item
62 end
62 end
63
63
64 # Renders the application main menu as a ul element
64 # Renders the application main menu as a ul element
65 def render_main_menu(project)
65 def render_main_menu(project)
66 links = []
66 links = []
67 Redmine::MenuManager.allowed_items(:project_menu, User.current, project).each do |item|
67 Redmine::MenuManager.allowed_items(:project_menu, User.current, project).each do |item|
68 unless item.condition && !item.condition.call(project)
68 unless item.condition && !item.condition.call(project)
69 links << content_tag('li',
69 links << content_tag('li',
70 link_to(l(item.caption), {item.param => project}.merge(item.url),
70 link_to(l(item.caption), {item.param => project}.merge(item.url),
71 :class => (current_menu_item == item.name ? 'selected' : nil)))
71 (current_menu_item == item.name ? item.html_options.merge(:class => 'selected') : item.html_options)))
72 end
72 end
73 end if project && !project.new_record?
73 end if project && !project.new_record?
74 links.empty? ? nil : content_tag('ul', links.join("\n"))
74 links.empty? ? nil : content_tag('ul', links.join("\n"))
75 end
75 end
76 end
76 end
77
77
78 class << self
78 class << self
79 def map(menu_name)
79 def map(menu_name)
80 mapper = Mapper.new
80 mapper = Mapper.new
81 yield mapper
81 yield mapper
82 @items ||= {}
82 @items ||= {}
83 @items[menu_name.to_sym] ||= []
83 @items[menu_name.to_sym] ||= []
84 @items[menu_name.to_sym] += mapper.items
84 @items[menu_name.to_sym] += mapper.items
85 end
85 end
86
86
87 def items(menu_name)
87 def items(menu_name)
88 @items[menu_name.to_sym] || []
88 @items[menu_name.to_sym] || []
89 end
89 end
90
90
91 def allowed_items(menu_name, user, project)
91 def allowed_items(menu_name, user, project)
92 items(menu_name).select {|item| user && user.allowed_to?(item.url, project)}
92 items(menu_name).select {|item| user && user.allowed_to?(item.url, project)}
93 end
93 end
94 end
94 end
95
95
96 class Mapper
96 class Mapper
97 # Adds an item at the end of the menu. Available options:
98 # * param: the parameter name that is used for the project id (default is :id)
99 # * condition: a proc that is called before rendering the item, the item is displayed only if it returns true
100 # * caption: the localized string key that is used as the item label
101 # * html_options: a hash of html options that are passed to link_to
97 def push(name, url, options={})
102 def push(name, url, options={})
98 @items ||= []
103 @items ||= []
99 @items << MenuItem.new(name, url, options)
104 @items << MenuItem.new(name, url, options)
100 end
105 end
101
106
102 def items
107 def items
103 @items
108 @items
104 end
109 end
105 end
110 end
106
111
107 class MenuItem
112 class MenuItem
108 attr_reader :name, :url, :param, :condition, :caption
113 attr_reader :name, :url, :param, :condition, :caption, :html_options
109
114
110 def initialize(name, url, options)
115 def initialize(name, url, options)
111 @name = name
116 @name = name
112 @url = url
117 @url = url
113 @condition = options[:if]
118 @condition = options[:if]
114 @param = options[:param] || :id
119 @param = options[:param] || :id
115 @caption = options[:caption] || name.to_s.humanize
120 @caption = options[:caption] || name.to_s.humanize
121 @html_options = options[:html] || {}
116 end
122 end
117 end
123 end
118 end
124 end
119 end
125 end
@@ -1,520 +1,520
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 a {color: #fff; padding-right: 4px;}
13 #top-menu a {color: #fff; padding-right: 4px;}
14 #account {float:right;}
14 #account {float:right;}
15
15
16 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
16 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
17 #header a {color:#f8f8f8;}
17 #header a {color:#f8f8f8;}
18 #quick-search {float:right;}
18 #quick-search {float:right;}
19
19
20 #main-menu {position: absolute; bottom: 0px; left:6px;}
20 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
21 #main-menu ul {margin: 0; padding: 0;}
21 #main-menu ul {margin: 0; padding: 0;}
22 #main-menu li {
22 #main-menu li {
23 float:left;
23 float:left;
24 list-style-type:none;
24 list-style-type:none;
25 margin: 0px 10px 0px 0px;
25 margin: 0px 10px 0px 0px;
26 padding: 0px 0px 0px 0px;
26 padding: 0px 0px 0px 0px;
27 white-space:nowrap;
27 white-space:nowrap;
28 }
28 }
29 #main-menu li a {
29 #main-menu li a {
30 display: block;
30 display: block;
31 color: #fff;
31 color: #fff;
32 text-decoration: none;
32 text-decoration: none;
33 margin: 0;
33 margin: 0;
34 padding: 4px 4px 4px 4px;
34 padding: 4px 4px 4px 4px;
35 background: #2C4056;
35 background: #2C4056;
36 }
36 }
37 #main-menu li a:hover, #main-menu li a.selected {background:#759FCF;}
37 #main-menu li a:hover, #main-menu li a.selected {background:#759FCF;}
38
38
39 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
39 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
40
40
41 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
41 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
42 * html #sidebar{ width: 17%; }
42 * html #sidebar{ width: 17%; }
43 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
43 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
44 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
44 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
45 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
45 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
46
46
47 #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;}
47 #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;}
48 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
48 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
49 html>body #content {
49 html>body #content {
50 height: auto;
50 height: auto;
51 min-height: 600px;
51 min-height: 600px;
52 }
52 }
53
53
54 #main.nosidebar #sidebar{ display: none; }
54 #main.nosidebar #sidebar{ display: none; }
55 #main.nosidebar #content{ width: auto; border-right: 0; }
55 #main.nosidebar #content{ width: auto; border-right: 0; }
56
56
57 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
57 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
58
58
59 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
59 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
60 #login-form table td {padding: 6px;}
60 #login-form table td {padding: 6px;}
61 #login-form label {font-weight: bold;}
61 #login-form label {font-weight: bold;}
62
62
63 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
63 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
64
64
65 /***** Links *****/
65 /***** Links *****/
66 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
66 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
67 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
67 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
68 a img{ border: 0; }
68 a img{ border: 0; }
69
69
70 /***** Tables *****/
70 /***** Tables *****/
71 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
71 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
72 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
72 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
73 table.list td { overflow: hidden; text-overflow: ellipsis; vertical-align: top;}
73 table.list td { overflow: hidden; text-overflow: ellipsis; vertical-align: top;}
74 table.list td.id { width: 2%; text-align: center;}
74 table.list td.id { width: 2%; text-align: center;}
75 table.list td.checkbox { width: 15px; padding: 0px;}
75 table.list td.checkbox { width: 15px; padding: 0px;}
76
76
77 tr.issue { text-align: center; white-space: nowrap; }
77 tr.issue { text-align: center; white-space: nowrap; }
78 tr.issue td.subject, tr.issue td.category { white-space: normal; }
78 tr.issue td.subject, tr.issue td.category { white-space: normal; }
79 tr.issue td.subject { text-align: left; }
79 tr.issue td.subject { text-align: left; }
80 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
80 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
81
81
82 tr.entry { border: 1px solid #f8f8f8; }
82 tr.entry { border: 1px solid #f8f8f8; }
83 tr.entry td { white-space: nowrap; }
83 tr.entry td { white-space: nowrap; }
84 tr.entry td.filename { width: 30%; }
84 tr.entry td.filename { width: 30%; }
85 tr.entry td.size { text-align: right; font-size: 90%; }
85 tr.entry td.size { text-align: right; font-size: 90%; }
86 tr.entry td.revision, tr.entry td.author { text-align: center; }
86 tr.entry td.revision, tr.entry td.author { text-align: center; }
87 tr.entry td.age { text-align: right; }
87 tr.entry td.age { text-align: right; }
88
88
89 tr.changeset td.author { text-align: center; width: 15%; }
89 tr.changeset td.author { text-align: center; width: 15%; }
90 tr.changeset td.committed_on { text-align: center; width: 15%; }
90 tr.changeset td.committed_on { text-align: center; width: 15%; }
91
91
92 tr.message { height: 2.6em; }
92 tr.message { height: 2.6em; }
93 tr.message td.last_message { font-size: 80%; }
93 tr.message td.last_message { font-size: 80%; }
94 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
94 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
95 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
95 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
96
96
97 table.list tbody tr:hover { background-color:#ffffdd; }
97 table.list tbody tr:hover { background-color:#ffffdd; }
98 table td {padding:2px;}
98 table td {padding:2px;}
99 table p {margin:0;}
99 table p {margin:0;}
100 .odd {background-color:#f6f7f8;}
100 .odd {background-color:#f6f7f8;}
101 .even {background-color: #fff;}
101 .even {background-color: #fff;}
102
102
103 .highlight { background-color: #FCFD8D;}
103 .highlight { background-color: #FCFD8D;}
104 .highlight.token-1 { background-color: #faa;}
104 .highlight.token-1 { background-color: #faa;}
105 .highlight.token-2 { background-color: #afa;}
105 .highlight.token-2 { background-color: #afa;}
106 .highlight.token-3 { background-color: #aaf;}
106 .highlight.token-3 { background-color: #aaf;}
107
107
108 .box{
108 .box{
109 padding:6px;
109 padding:6px;
110 margin-bottom: 10px;
110 margin-bottom: 10px;
111 background-color:#f6f6f6;
111 background-color:#f6f6f6;
112 color:#505050;
112 color:#505050;
113 line-height:1.5em;
113 line-height:1.5em;
114 border: 1px solid #e4e4e4;
114 border: 1px solid #e4e4e4;
115 }
115 }
116
116
117 div.square {
117 div.square {
118 border: 1px solid #999;
118 border: 1px solid #999;
119 float: left;
119 float: left;
120 margin: .3em .4em 0 .4em;
120 margin: .3em .4em 0 .4em;
121 overflow: hidden;
121 overflow: hidden;
122 width: .6em; height: .6em;
122 width: .6em; height: .6em;
123 }
123 }
124
124
125 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px;font-size:0.9em;}
125 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px;font-size:0.9em;}
126 .contextual input {font-size:0.9em;}
126 .contextual input {font-size:0.9em;}
127
127
128 .splitcontentleft{float:left; width:49%;}
128 .splitcontentleft{float:left; width:49%;}
129 .splitcontentright{float:right; width:49%;}
129 .splitcontentright{float:right; width:49%;}
130 form {display: inline;}
130 form {display: inline;}
131 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
131 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
132 fieldset {border: 1px solid #e4e4e4; margin:0;}
132 fieldset {border: 1px solid #e4e4e4; margin:0;}
133 legend {color: #484848;}
133 legend {color: #484848;}
134 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
134 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
135 textarea.wiki-edit { width: 99%; }
135 textarea.wiki-edit { width: 99%; }
136 li p {margin-top: 0;}
136 li p {margin-top: 0;}
137 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
137 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
138 .autoscroll {overflow-x: auto; padding:1px; width:100%; margin-bottom: 1.2em;}
138 .autoscroll {overflow-x: auto; padding:1px; width:100%; margin-bottom: 1.2em;}
139 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
139 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
140
140
141 .pagination {font-size: 90%}
141 .pagination {font-size: 90%}
142 p.pagination {margin-top:8px;}
142 p.pagination {margin-top:8px;}
143
143
144 /***** Tabular forms ******/
144 /***** Tabular forms ******/
145 .tabular p{
145 .tabular p{
146 margin: 0;
146 margin: 0;
147 padding: 5px 0 8px 0;
147 padding: 5px 0 8px 0;
148 padding-left: 180px; /*width of left column containing the label elements*/
148 padding-left: 180px; /*width of left column containing the label elements*/
149 height: 1%;
149 height: 1%;
150 clear:left;
150 clear:left;
151 }
151 }
152
152
153 .tabular label{
153 .tabular label{
154 font-weight: bold;
154 font-weight: bold;
155 float: left;
155 float: left;
156 text-align: right;
156 text-align: right;
157 margin-left: -180px; /*width of left column*/
157 margin-left: -180px; /*width of left column*/
158 width: 175px; /*width of labels. Should be smaller than left column to create some right
158 width: 175px; /*width of labels. Should be smaller than left column to create some right
159 margin*/
159 margin*/
160 }
160 }
161
161
162 .tabular label.floating{
162 .tabular label.floating{
163 font-weight: normal;
163 font-weight: normal;
164 margin-left: 0px;
164 margin-left: 0px;
165 text-align: left;
165 text-align: left;
166 width: 200px;
166 width: 200px;
167 }
167 }
168
168
169 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
169 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
170
170
171 .tabular.settings p{ padding-left: 300px; }
171 .tabular.settings p{ padding-left: 300px; }
172 .tabular.settings label{ margin-left: -300px; width: 295px; }
172 .tabular.settings label{ margin-left: -300px; width: 295px; }
173
173
174 .required {color: #bb0000;}
174 .required {color: #bb0000;}
175 .summary {font-style: italic;}
175 .summary {font-style: italic;}
176
176
177 div.attachments p { margin:4px 0 2px 0; }
177 div.attachments p { margin:4px 0 2px 0; }
178
178
179 /***** Flash & error messages ****/
179 /***** Flash & error messages ****/
180 #errorExplanation, div.flash, .nodata {
180 #errorExplanation, div.flash, .nodata {
181 padding: 4px 4px 4px 30px;
181 padding: 4px 4px 4px 30px;
182 margin-bottom: 12px;
182 margin-bottom: 12px;
183 font-size: 1.1em;
183 font-size: 1.1em;
184 border: 2px solid;
184 border: 2px solid;
185 }
185 }
186
186
187 div.flash {margin-top: 8px;}
187 div.flash {margin-top: 8px;}
188
188
189 div.flash.error, #errorExplanation {
189 div.flash.error, #errorExplanation {
190 background: url(../images/false.png) 8px 5px no-repeat;
190 background: url(../images/false.png) 8px 5px no-repeat;
191 background-color: #ffe3e3;
191 background-color: #ffe3e3;
192 border-color: #dd0000;
192 border-color: #dd0000;
193 color: #550000;
193 color: #550000;
194 }
194 }
195
195
196 div.flash.notice {
196 div.flash.notice {
197 background: url(../images/true.png) 8px 5px no-repeat;
197 background: url(../images/true.png) 8px 5px no-repeat;
198 background-color: #dfffdf;
198 background-color: #dfffdf;
199 border-color: #9fcf9f;
199 border-color: #9fcf9f;
200 color: #005f00;
200 color: #005f00;
201 }
201 }
202
202
203 .nodata {
203 .nodata {
204 text-align: center;
204 text-align: center;
205 background-color: #FFEBC1;
205 background-color: #FFEBC1;
206 border-color: #FDBF3B;
206 border-color: #FDBF3B;
207 color: #A6750C;
207 color: #A6750C;
208 }
208 }
209
209
210 #errorExplanation ul { font-size: 0.9em;}
210 #errorExplanation ul { font-size: 0.9em;}
211
211
212 /***** Ajax indicator ******/
212 /***** Ajax indicator ******/
213 #ajax-indicator {
213 #ajax-indicator {
214 position: absolute; /* fixed not supported by IE */
214 position: absolute; /* fixed not supported by IE */
215 background-color:#eee;
215 background-color:#eee;
216 border: 1px solid #bbb;
216 border: 1px solid #bbb;
217 top:35%;
217 top:35%;
218 left:40%;
218 left:40%;
219 width:20%;
219 width:20%;
220 font-weight:bold;
220 font-weight:bold;
221 text-align:center;
221 text-align:center;
222 padding:0.6em;
222 padding:0.6em;
223 z-index:100;
223 z-index:100;
224 filter:alpha(opacity=50);
224 filter:alpha(opacity=50);
225 -moz-opacity:0.5;
225 -moz-opacity:0.5;
226 opacity: 0.5;
226 opacity: 0.5;
227 -khtml-opacity: 0.5;
227 -khtml-opacity: 0.5;
228 }
228 }
229
229
230 html>body #ajax-indicator { position: fixed; }
230 html>body #ajax-indicator { position: fixed; }
231
231
232 #ajax-indicator span {
232 #ajax-indicator span {
233 background-position: 0% 40%;
233 background-position: 0% 40%;
234 background-repeat: no-repeat;
234 background-repeat: no-repeat;
235 background-image: url(../images/loading.gif);
235 background-image: url(../images/loading.gif);
236 padding-left: 26px;
236 padding-left: 26px;
237 vertical-align: bottom;
237 vertical-align: bottom;
238 }
238 }
239
239
240 /***** Calendar *****/
240 /***** Calendar *****/
241 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
241 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
242 table.cal thead th {width: 14%;}
242 table.cal thead th {width: 14%;}
243 table.cal tbody tr {height: 100px;}
243 table.cal tbody tr {height: 100px;}
244 table.cal th { background-color:#EEEEEE; padding: 4px; }
244 table.cal th { background-color:#EEEEEE; padding: 4px; }
245 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
245 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
246 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
246 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
247 table.cal td.odd p.day-num {color: #bbb;}
247 table.cal td.odd p.day-num {color: #bbb;}
248 table.cal td.today {background:#ffffdd;}
248 table.cal td.today {background:#ffffdd;}
249 table.cal td.today p.day-num {font-weight: bold;}
249 table.cal td.today p.day-num {font-weight: bold;}
250
250
251 /***** Tooltips ******/
251 /***** Tooltips ******/
252 .tooltip{position:relative;z-index:24;}
252 .tooltip{position:relative;z-index:24;}
253 .tooltip:hover{z-index:25;color:#000;}
253 .tooltip:hover{z-index:25;color:#000;}
254 .tooltip span.tip{display: none; text-align:left;}
254 .tooltip span.tip{display: none; text-align:left;}
255
255
256 div.tooltip:hover span.tip{
256 div.tooltip:hover span.tip{
257 display:block;
257 display:block;
258 position:absolute;
258 position:absolute;
259 top:12px; left:24px; width:270px;
259 top:12px; left:24px; width:270px;
260 border:1px solid #555;
260 border:1px solid #555;
261 background-color:#fff;
261 background-color:#fff;
262 padding: 4px;
262 padding: 4px;
263 font-size: 0.8em;
263 font-size: 0.8em;
264 color:#505050;
264 color:#505050;
265 }
265 }
266
266
267 /***** Progress bar *****/
267 /***** Progress bar *****/
268 table.progress {
268 table.progress {
269 border: 1px solid #D7D7D7;
269 border: 1px solid #D7D7D7;
270 border-collapse: collapse;
270 border-collapse: collapse;
271 border-spacing: 0pt;
271 border-spacing: 0pt;
272 empty-cells: show;
272 empty-cells: show;
273 text-align: center;
273 text-align: center;
274 float:left;
274 float:left;
275 margin: 1px 6px 1px 0px;
275 margin: 1px 6px 1px 0px;
276 }
276 }
277
277
278 table.progress td { height: 0.9em; }
278 table.progress td { height: 0.9em; }
279 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
279 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
280 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
280 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
281 table.progress td.open { background: #FFF none repeat scroll 0%; }
281 table.progress td.open { background: #FFF none repeat scroll 0%; }
282 p.pourcent {font-size: 80%;}
282 p.pourcent {font-size: 80%;}
283 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
283 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
284
284
285 div#status_by { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; }
285 div#status_by { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; }
286
286
287 /***** Tabs *****/
287 /***** Tabs *****/
288 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
288 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
289 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
289 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
290 #content .tabs>ul { bottom:-1px; } /* others */
290 #content .tabs>ul { bottom:-1px; } /* others */
291 #content .tabs ul li {
291 #content .tabs ul li {
292 float:left;
292 float:left;
293 list-style-type:none;
293 list-style-type:none;
294 white-space:nowrap;
294 white-space:nowrap;
295 margin-right:8px;
295 margin-right:8px;
296 background:#fff;
296 background:#fff;
297 }
297 }
298 #content .tabs ul li a{
298 #content .tabs ul li a{
299 display:block;
299 display:block;
300 font-size: 0.9em;
300 font-size: 0.9em;
301 text-decoration:none;
301 text-decoration:none;
302 line-height:1.3em;
302 line-height:1.3em;
303 padding:4px 6px 4px 6px;
303 padding:4px 6px 4px 6px;
304 border: 1px solid #ccc;
304 border: 1px solid #ccc;
305 border-bottom: 1px solid #bbbbbb;
305 border-bottom: 1px solid #bbbbbb;
306 background-color: #eeeeee;
306 background-color: #eeeeee;
307 color:#777;
307 color:#777;
308 font-weight:bold;
308 font-weight:bold;
309 }
309 }
310
310
311 #content .tabs ul li a:hover {
311 #content .tabs ul li a:hover {
312 background-color: #ffffdd;
312 background-color: #ffffdd;
313 text-decoration:none;
313 text-decoration:none;
314 }
314 }
315
315
316 #content .tabs ul li a.selected {
316 #content .tabs ul li a.selected {
317 background-color: #fff;
317 background-color: #fff;
318 border: 1px solid #bbbbbb;
318 border: 1px solid #bbbbbb;
319 border-bottom: 1px solid #fff;
319 border-bottom: 1px solid #fff;
320 }
320 }
321
321
322 #content .tabs ul li a.selected:hover {
322 #content .tabs ul li a.selected:hover {
323 background-color: #fff;
323 background-color: #fff;
324 }
324 }
325
325
326 /***** Diff *****/
326 /***** Diff *****/
327 .diff_out { background: #fcc; }
327 .diff_out { background: #fcc; }
328 .diff_in { background: #cfc; }
328 .diff_in { background: #cfc; }
329
329
330 /***** Wiki *****/
330 /***** Wiki *****/
331 div.wiki table {
331 div.wiki table {
332 border: 1px solid #505050;
332 border: 1px solid #505050;
333 border-collapse: collapse;
333 border-collapse: collapse;
334 }
334 }
335
335
336 div.wiki table, div.wiki td, div.wiki th {
336 div.wiki table, div.wiki td, div.wiki th {
337 border: 1px solid #bbb;
337 border: 1px solid #bbb;
338 padding: 4px;
338 padding: 4px;
339 }
339 }
340
340
341 div.wiki .external {
341 div.wiki .external {
342 background-position: 0% 60%;
342 background-position: 0% 60%;
343 background-repeat: no-repeat;
343 background-repeat: no-repeat;
344 padding-left: 12px;
344 padding-left: 12px;
345 background-image: url(../images/external.png);
345 background-image: url(../images/external.png);
346 }
346 }
347
347
348 div.wiki a.new {
348 div.wiki a.new {
349 color: #b73535;
349 color: #b73535;
350 }
350 }
351
351
352 div.wiki pre {
352 div.wiki pre {
353 margin: 1em 1em 1em 1.6em;
353 margin: 1em 1em 1em 1.6em;
354 padding: 2px;
354 padding: 2px;
355 background-color: #fafafa;
355 background-color: #fafafa;
356 border: 1px solid #dadada;
356 border: 1px solid #dadada;
357 width:95%;
357 width:95%;
358 overflow-x: auto;
358 overflow-x: auto;
359 }
359 }
360
360
361 div.wiki div.toc {
361 div.wiki div.toc {
362 background-color: #ffffdd;
362 background-color: #ffffdd;
363 border: 1px solid #e4e4e4;
363 border: 1px solid #e4e4e4;
364 padding: 4px;
364 padding: 4px;
365 line-height: 1.2em;
365 line-height: 1.2em;
366 margin-bottom: 12px;
366 margin-bottom: 12px;
367 margin-right: 12px;
367 margin-right: 12px;
368 display: table
368 display: table
369 }
369 }
370 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
370 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
371
371
372 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
372 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
373 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
373 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
374
374
375 div.wiki div.toc a {
375 div.wiki div.toc a {
376 display: block;
376 display: block;
377 font-size: 0.9em;
377 font-size: 0.9em;
378 font-weight: normal;
378 font-weight: normal;
379 text-decoration: none;
379 text-decoration: none;
380 color: #606060;
380 color: #606060;
381 }
381 }
382 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
382 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
383
383
384 div.wiki div.toc a.heading2 { margin-left: 6px; }
384 div.wiki div.toc a.heading2 { margin-left: 6px; }
385 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
385 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
386
386
387 /***** My page layout *****/
387 /***** My page layout *****/
388 .block-receiver {
388 .block-receiver {
389 border:1px dashed #c0c0c0;
389 border:1px dashed #c0c0c0;
390 margin-bottom: 20px;
390 margin-bottom: 20px;
391 padding: 15px 0 15px 0;
391 padding: 15px 0 15px 0;
392 }
392 }
393
393
394 .mypage-box {
394 .mypage-box {
395 margin:0 0 20px 0;
395 margin:0 0 20px 0;
396 color:#505050;
396 color:#505050;
397 line-height:1.5em;
397 line-height:1.5em;
398 }
398 }
399
399
400 .handle {
400 .handle {
401 cursor: move;
401 cursor: move;
402 }
402 }
403
403
404 a.close-icon {
404 a.close-icon {
405 display:block;
405 display:block;
406 margin-top:3px;
406 margin-top:3px;
407 overflow:hidden;
407 overflow:hidden;
408 width:12px;
408 width:12px;
409 height:12px;
409 height:12px;
410 background-repeat: no-repeat;
410 background-repeat: no-repeat;
411 cursor:pointer;
411 cursor:pointer;
412 background-image:url('../images/close.png');
412 background-image:url('../images/close.png');
413 }
413 }
414
414
415 a.close-icon:hover {
415 a.close-icon:hover {
416 background-image:url('../images/close_hl.png');
416 background-image:url('../images/close_hl.png');
417 }
417 }
418
418
419 /***** Gantt chart *****/
419 /***** Gantt chart *****/
420 .gantt_hdr {
420 .gantt_hdr {
421 position:absolute;
421 position:absolute;
422 top:0;
422 top:0;
423 height:16px;
423 height:16px;
424 border-top: 1px solid #c0c0c0;
424 border-top: 1px solid #c0c0c0;
425 border-bottom: 1px solid #c0c0c0;
425 border-bottom: 1px solid #c0c0c0;
426 border-right: 1px solid #c0c0c0;
426 border-right: 1px solid #c0c0c0;
427 text-align: center;
427 text-align: center;
428 overflow: hidden;
428 overflow: hidden;
429 }
429 }
430
430
431 .task {
431 .task {
432 position: absolute;
432 position: absolute;
433 height:8px;
433 height:8px;
434 font-size:0.8em;
434 font-size:0.8em;
435 color:#888;
435 color:#888;
436 padding:0;
436 padding:0;
437 margin:0;
437 margin:0;
438 line-height:0.8em;
438 line-height:0.8em;
439 }
439 }
440
440
441 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
441 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
442 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
442 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
443 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
443 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
444 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
444 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
445
445
446 /***** Icons *****/
446 /***** Icons *****/
447 .icon {
447 .icon {
448 background-position: 0% 40%;
448 background-position: 0% 40%;
449 background-repeat: no-repeat;
449 background-repeat: no-repeat;
450 padding-left: 20px;
450 padding-left: 20px;
451 padding-top: 2px;
451 padding-top: 2px;
452 padding-bottom: 3px;
452 padding-bottom: 3px;
453 }
453 }
454
454
455 .icon22 {
455 .icon22 {
456 background-position: 0% 40%;
456 background-position: 0% 40%;
457 background-repeat: no-repeat;
457 background-repeat: no-repeat;
458 padding-left: 26px;
458 padding-left: 26px;
459 line-height: 22px;
459 line-height: 22px;
460 vertical-align: middle;
460 vertical-align: middle;
461 }
461 }
462
462
463 .icon-add { background-image: url(../images/add.png); }
463 .icon-add { background-image: url(../images/add.png); }
464 .icon-edit { background-image: url(../images/edit.png); }
464 .icon-edit { background-image: url(../images/edit.png); }
465 .icon-copy { background-image: url(../images/copy.png); }
465 .icon-copy { background-image: url(../images/copy.png); }
466 .icon-del { background-image: url(../images/delete.png); }
466 .icon-del { background-image: url(../images/delete.png); }
467 .icon-move { background-image: url(../images/move.png); }
467 .icon-move { background-image: url(../images/move.png); }
468 .icon-save { background-image: url(../images/save.png); }
468 .icon-save { background-image: url(../images/save.png); }
469 .icon-cancel { background-image: url(../images/cancel.png); }
469 .icon-cancel { background-image: url(../images/cancel.png); }
470 .icon-pdf { background-image: url(../images/pdf.png); }
470 .icon-pdf { background-image: url(../images/pdf.png); }
471 .icon-csv { background-image: url(../images/csv.png); }
471 .icon-csv { background-image: url(../images/csv.png); }
472 .icon-html { background-image: url(../images/html.png); }
472 .icon-html { background-image: url(../images/html.png); }
473 .icon-image { background-image: url(../images/image.png); }
473 .icon-image { background-image: url(../images/image.png); }
474 .icon-txt { background-image: url(../images/txt.png); }
474 .icon-txt { background-image: url(../images/txt.png); }
475 .icon-file { background-image: url(../images/file.png); }
475 .icon-file { background-image: url(../images/file.png); }
476 .icon-folder { background-image: url(../images/folder.png); }
476 .icon-folder { background-image: url(../images/folder.png); }
477 .open .icon-folder { background-image: url(../images/folder_open.png); }
477 .open .icon-folder { background-image: url(../images/folder_open.png); }
478 .icon-package { background-image: url(../images/package.png); }
478 .icon-package { background-image: url(../images/package.png); }
479 .icon-home { background-image: url(../images/home.png); }
479 .icon-home { background-image: url(../images/home.png); }
480 .icon-user { background-image: url(../images/user.png); }
480 .icon-user { background-image: url(../images/user.png); }
481 .icon-mypage { background-image: url(../images/user_page.png); }
481 .icon-mypage { background-image: url(../images/user_page.png); }
482 .icon-admin { background-image: url(../images/admin.png); }
482 .icon-admin { background-image: url(../images/admin.png); }
483 .icon-projects { background-image: url(../images/projects.png); }
483 .icon-projects { background-image: url(../images/projects.png); }
484 .icon-logout { background-image: url(../images/logout.png); }
484 .icon-logout { background-image: url(../images/logout.png); }
485 .icon-help { background-image: url(../images/help.png); }
485 .icon-help { background-image: url(../images/help.png); }
486 .icon-attachment { background-image: url(../images/attachment.png); }
486 .icon-attachment { background-image: url(../images/attachment.png); }
487 .icon-index { background-image: url(../images/index.png); }
487 .icon-index { background-image: url(../images/index.png); }
488 .icon-history { background-image: url(../images/history.png); }
488 .icon-history { background-image: url(../images/history.png); }
489 .icon-feed { background-image: url(../images/feed.png); }
489 .icon-feed { background-image: url(../images/feed.png); }
490 .icon-time { background-image: url(../images/time.png); }
490 .icon-time { background-image: url(../images/time.png); }
491 .icon-stats { background-image: url(../images/stats.png); }
491 .icon-stats { background-image: url(../images/stats.png); }
492 .icon-warning { background-image: url(../images/warning.png); }
492 .icon-warning { background-image: url(../images/warning.png); }
493 .icon-fav { background-image: url(../images/fav.png); }
493 .icon-fav { background-image: url(../images/fav.png); }
494 .icon-fav-off { background-image: url(../images/fav_off.png); }
494 .icon-fav-off { background-image: url(../images/fav_off.png); }
495 .icon-reload { background-image: url(../images/reload.png); }
495 .icon-reload { background-image: url(../images/reload.png); }
496 .icon-lock { background-image: url(../images/locked.png); }
496 .icon-lock { background-image: url(../images/locked.png); }
497 .icon-unlock { background-image: url(../images/unlock.png); }
497 .icon-unlock { background-image: url(../images/unlock.png); }
498 .icon-note { background-image: url(../images/note.png); }
498 .icon-note { background-image: url(../images/note.png); }
499 .icon-checked { background-image: url(../images/true.png); }
499 .icon-checked { background-image: url(../images/true.png); }
500
500
501 .icon22-projects { background-image: url(../images/22x22/projects.png); }
501 .icon22-projects { background-image: url(../images/22x22/projects.png); }
502 .icon22-users { background-image: url(../images/22x22/users.png); }
502 .icon22-users { background-image: url(../images/22x22/users.png); }
503 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
503 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
504 .icon22-role { background-image: url(../images/22x22/role.png); }
504 .icon22-role { background-image: url(../images/22x22/role.png); }
505 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
505 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
506 .icon22-options { background-image: url(../images/22x22/options.png); }
506 .icon22-options { background-image: url(../images/22x22/options.png); }
507 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
507 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
508 .icon22-authent { background-image: url(../images/22x22/authent.png); }
508 .icon22-authent { background-image: url(../images/22x22/authent.png); }
509 .icon22-info { background-image: url(../images/22x22/info.png); }
509 .icon22-info { background-image: url(../images/22x22/info.png); }
510 .icon22-comment { background-image: url(../images/22x22/comment.png); }
510 .icon22-comment { background-image: url(../images/22x22/comment.png); }
511 .icon22-package { background-image: url(../images/22x22/package.png); }
511 .icon22-package { background-image: url(../images/22x22/package.png); }
512 .icon22-settings { background-image: url(../images/22x22/settings.png); }
512 .icon22-settings { background-image: url(../images/22x22/settings.png); }
513 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
513 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
514
514
515 /***** Media print specific styles *****/
515 /***** Media print specific styles *****/
516 @media print {
516 @media print {
517 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
517 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
518 #main { background: #fff; }
518 #main { background: #fff; }
519 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
519 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
520 }
520 }
General Comments 0
You need to be logged in to leave comments. Login now