##// END OF EJS Templates
Adds pagination to users list when adding project or group members (#9549)....
Jean-Philippe Lang -
r10970:1257f1960ff6
parent child
Show More
@@ -0,0 +1,1
1 $('#users').html('<%= escape_javascript(render_principals_for_new_group_users(@group)) %>');
@@ -0,0 +1,1
1 $('#principals_for_new_member').html('<%= escape_javascript(render_principals_for_new_members(@project)) %>');
@@ -1,140 +1,141
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class GroupsController < ApplicationController
19 19 layout 'admin'
20 20
21 21 before_filter :require_admin
22 22 before_filter :find_group, :except => [:index, :new, :create]
23 23 accept_api_auth :index, :show, :create, :update, :destroy, :add_users, :remove_user
24 24
25 25 helper :custom_fields
26 26
27 27 def index
28 28 @groups = Group.sorted.all
29 29
30 30 respond_to do |format|
31 31 format.html
32 32 format.api
33 33 end
34 34 end
35 35
36 36 def show
37 37 respond_to do |format|
38 38 format.html
39 39 format.api
40 40 end
41 41 end
42 42
43 43 def new
44 44 @group = Group.new
45 45 end
46 46
47 47 def create
48 48 @group = Group.new
49 49 @group.safe_attributes = params[:group]
50 50
51 51 respond_to do |format|
52 52 if @group.save
53 53 format.html {
54 54 flash[:notice] = l(:notice_successful_create)
55 55 redirect_to(params[:continue] ? new_group_path : groups_path)
56 56 }
57 57 format.api { render :action => 'show', :status => :created, :location => group_url(@group) }
58 58 else
59 59 format.html { render :action => "new" }
60 60 format.api { render_validation_errors(@group) }
61 61 end
62 62 end
63 63 end
64 64
65 65 def edit
66 66 end
67 67
68 68 def update
69 69 @group.safe_attributes = params[:group]
70 70
71 71 respond_to do |format|
72 72 if @group.save
73 73 flash[:notice] = l(:notice_successful_update)
74 74 format.html { redirect_to(groups_path) }
75 75 format.api { render_api_ok }
76 76 else
77 77 format.html { render :action => "edit" }
78 78 format.api { render_validation_errors(@group) }
79 79 end
80 80 end
81 81 end
82 82
83 83 def destroy
84 84 @group.destroy
85 85
86 86 respond_to do |format|
87 87 format.html { redirect_to(groups_path) }
88 88 format.api { render_api_ok }
89 89 end
90 90 end
91 91
92 92 def add_users
93 93 @users = User.find_all_by_id(params[:user_id] || params[:user_ids])
94 94 @group.users << @users if request.post?
95 95 respond_to do |format|
96 96 format.html { redirect_to edit_group_path(@group, :tab => 'users') }
97 97 format.js
98 98 format.api { render_api_ok }
99 99 end
100 100 end
101 101
102 102 def remove_user
103 103 @group.users.delete(User.find(params[:user_id])) if request.delete?
104 104 respond_to do |format|
105 105 format.html { redirect_to edit_group_path(@group, :tab => 'users') }
106 106 format.js
107 107 format.api { render_api_ok }
108 108 end
109 109 end
110 110
111 111 def autocomplete_for_user
112 @users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100)
113 render :layout => false
112 respond_to do |format|
113 format.js
114 end
114 115 end
115 116
116 117 def edit_membership
117 118 @membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
118 119 @membership.save if request.post?
119 120 respond_to do |format|
120 121 format.html { redirect_to edit_group_path(@group, :tab => 'memberships') }
121 122 format.js
122 123 end
123 124 end
124 125
125 126 def destroy_membership
126 127 Member.find(params[:membership_id]).destroy if request.post?
127 128 respond_to do |format|
128 129 format.html { redirect_to edit_group_path(@group, :tab => 'memberships') }
129 130 format.js
130 131 end
131 132 end
132 133
133 134 private
134 135
135 136 def find_group
136 137 @group = Group.find(params[:id])
137 138 rescue ActiveRecord::RecordNotFound
138 139 render_404
139 140 end
140 141 end
@@ -1,124 +1,125
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class MembersController < ApplicationController
19 19 model_object Member
20 20 before_filter :find_model_object, :except => [:index, :create, :autocomplete]
21 21 before_filter :find_project_from_association, :except => [:index, :create, :autocomplete]
22 22 before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete]
23 23 before_filter :authorize
24 24 accept_api_auth :index, :show, :create, :update, :destroy
25 25
26 26 def index
27 27 @offset, @limit = api_offset_and_limit
28 28 @member_count = @project.member_principals.count
29 29 @member_pages = Paginator.new @member_count, @limit, params['page']
30 30 @offset ||= @member_pages.offset
31 31 @members = @project.member_principals.all(
32 32 :order => "#{Member.table_name}.id",
33 33 :limit => @limit,
34 34 :offset => @offset
35 35 )
36 36
37 37 respond_to do |format|
38 38 format.html { head 406 }
39 39 format.api
40 40 end
41 41 end
42 42
43 43 def show
44 44 respond_to do |format|
45 45 format.html { head 406 }
46 46 format.api
47 47 end
48 48 end
49 49
50 50 def create
51 51 members = []
52 52 if params[:membership]
53 53 if params[:membership][:user_ids]
54 54 attrs = params[:membership].dup
55 55 user_ids = attrs.delete(:user_ids)
56 56 user_ids.each do |user_id|
57 57 members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id)
58 58 end
59 59 else
60 60 members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id])
61 61 end
62 62 @project.members << members
63 63 end
64 64
65 65 respond_to do |format|
66 66 format.html { redirect_to_settings_in_projects }
67 67 format.js { @members = members }
68 68 format.api {
69 69 @member = members.first
70 70 if @member.valid?
71 71 render :action => 'show', :status => :created, :location => membership_url(@member)
72 72 else
73 73 render_validation_errors(@member)
74 74 end
75 75 }
76 76 end
77 77 end
78 78
79 79 def update
80 80 if params[:membership]
81 81 @member.role_ids = params[:membership][:role_ids]
82 82 end
83 83 saved = @member.save
84 84 respond_to do |format|
85 85 format.html { redirect_to_settings_in_projects }
86 86 format.js
87 87 format.api {
88 88 if saved
89 89 render_api_ok
90 90 else
91 91 render_validation_errors(@member)
92 92 end
93 93 }
94 94 end
95 95 end
96 96
97 97 def destroy
98 98 if request.delete? && @member.deletable?
99 99 @member.destroy
100 100 end
101 101 respond_to do |format|
102 102 format.html { redirect_to_settings_in_projects }
103 103 format.js
104 104 format.api {
105 105 if @member.destroyed?
106 106 render_api_ok
107 107 else
108 108 head :unprocessable_entity
109 109 end
110 110 }
111 111 end
112 112 end
113 113
114 114 def autocomplete
115 @principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100)
116 render :layout => false
115 respond_to do |format|
116 format.js
117 end
117 118 end
118 119
119 120 private
120 121
121 122 def redirect_to_settings_in_projects
122 123 redirect_to settings_project_path(@project, :tab => 'members')
123 124 end
124 125 end
@@ -1,261 +1,262
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class ProjectsController < ApplicationController
19 19 menu_item :overview
20 20 menu_item :roadmap, :only => :roadmap
21 21 menu_item :settings, :only => :settings
22 22
23 23 before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
24 24 before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
25 25 before_filter :authorize_global, :only => [:new, :create]
26 26 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
27 27 accept_rss_auth :index
28 28 accept_api_auth :index, :show, :create, :update, :destroy
29 29
30 30 after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
31 31 if controller.request.post?
32 32 controller.send :expire_action, :controller => 'welcome', :action => 'robots'
33 33 end
34 34 end
35 35
36 36 helper :sort
37 37 include SortHelper
38 38 helper :custom_fields
39 39 include CustomFieldsHelper
40 40 helper :issues
41 41 helper :queries
42 42 include QueriesHelper
43 43 helper :repositories
44 44 include RepositoriesHelper
45 45 include ProjectsHelper
46 helper :members
46 47
47 48 # Lists visible projects
48 49 def index
49 50 respond_to do |format|
50 51 format.html {
51 52 scope = Project
52 53 unless params[:closed]
53 54 scope = scope.active
54 55 end
55 56 @projects = scope.visible.order('lft').all
56 57 }
57 58 format.api {
58 59 @offset, @limit = api_offset_and_limit
59 60 @project_count = Project.visible.count
60 61 @projects = Project.visible.offset(@offset).limit(@limit).order('lft').all
61 62 }
62 63 format.atom {
63 64 projects = Project.visible.order('created_on DESC').limit(Setting.feeds_limit.to_i).all
64 65 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
65 66 }
66 67 end
67 68 end
68 69
69 70 def new
70 71 @issue_custom_fields = IssueCustomField.sorted.all
71 72 @trackers = Tracker.sorted.all
72 73 @project = Project.new
73 74 @project.safe_attributes = params[:project]
74 75 end
75 76
76 77 def create
77 78 @issue_custom_fields = IssueCustomField.sorted.all
78 79 @trackers = Tracker.sorted.all
79 80 @project = Project.new
80 81 @project.safe_attributes = params[:project]
81 82
82 83 if validate_parent_id && @project.save
83 84 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
84 85 # Add current user as a project member if he is not admin
85 86 unless User.current.admin?
86 87 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
87 88 m = Member.new(:user => User.current, :roles => [r])
88 89 @project.members << m
89 90 end
90 91 respond_to do |format|
91 92 format.html {
92 93 flash[:notice] = l(:notice_successful_create)
93 94 if params[:continue]
94 95 attrs = {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}
95 96 redirect_to new_project_path(attrs)
96 97 else
97 98 redirect_to settings_project_path(@project)
98 99 end
99 100 }
100 101 format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
101 102 end
102 103 else
103 104 respond_to do |format|
104 105 format.html { render :action => 'new' }
105 106 format.api { render_validation_errors(@project) }
106 107 end
107 108 end
108 109 end
109 110
110 111 def copy
111 112 @issue_custom_fields = IssueCustomField.sorted.all
112 113 @trackers = Tracker.sorted.all
113 114 @source_project = Project.find(params[:id])
114 115 if request.get?
115 116 @project = Project.copy_from(@source_project)
116 117 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
117 118 else
118 119 Mailer.with_deliveries(params[:notifications] == '1') do
119 120 @project = Project.new
120 121 @project.safe_attributes = params[:project]
121 122 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
122 123 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
123 124 flash[:notice] = l(:notice_successful_create)
124 125 redirect_to settings_project_path(@project)
125 126 elsif !@project.new_record?
126 127 # Project was created
127 128 # But some objects were not copied due to validation failures
128 129 # (eg. issues from disabled trackers)
129 130 # TODO: inform about that
130 131 redirect_to settings_project_path(@project)
131 132 end
132 133 end
133 134 end
134 135 rescue ActiveRecord::RecordNotFound
135 136 # source_project not found
136 137 render_404
137 138 end
138 139
139 140 # Show @project
140 141 def show
141 142 if params[:jump]
142 143 # try to redirect to the requested menu item
143 144 redirect_to_project_menu_item(@project, params[:jump]) && return
144 145 end
145 146
146 147 @users_by_role = @project.users_by_role
147 148 @subprojects = @project.children.visible.all
148 149 @news = @project.news.limit(5).includes(:author, :project).reorder("#{News.table_name}.created_on DESC").all
149 150 @trackers = @project.rolled_up_trackers
150 151
151 152 cond = @project.project_condition(Setting.display_subprojects_issues?)
152 153
153 154 @open_issues_by_tracker = Issue.visible.open.where(cond).count(:group => :tracker)
154 155 @total_issues_by_tracker = Issue.visible.where(cond).count(:group => :tracker)
155 156
156 157 if User.current.allowed_to?(:view_time_entries, @project)
157 158 @total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
158 159 end
159 160
160 161 @key = User.current.rss_key
161 162
162 163 respond_to do |format|
163 164 format.html
164 165 format.api
165 166 end
166 167 end
167 168
168 169 def settings
169 170 @issue_custom_fields = IssueCustomField.sorted.all
170 171 @issue_category ||= IssueCategory.new
171 172 @member ||= @project.members.new
172 173 @trackers = Tracker.sorted.all
173 174 @wiki ||= @project.wiki
174 175 end
175 176
176 177 def edit
177 178 end
178 179
179 180 def update
180 181 @project.safe_attributes = params[:project]
181 182 if validate_parent_id && @project.save
182 183 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
183 184 respond_to do |format|
184 185 format.html {
185 186 flash[:notice] = l(:notice_successful_update)
186 187 redirect_to settings_project_path(@project)
187 188 }
188 189 format.api { render_api_ok }
189 190 end
190 191 else
191 192 respond_to do |format|
192 193 format.html {
193 194 settings
194 195 render :action => 'settings'
195 196 }
196 197 format.api { render_validation_errors(@project) }
197 198 end
198 199 end
199 200 end
200 201
201 202 def modules
202 203 @project.enabled_module_names = params[:enabled_module_names]
203 204 flash[:notice] = l(:notice_successful_update)
204 205 redirect_to settings_project_path(@project, :tab => 'modules')
205 206 end
206 207
207 208 def archive
208 209 if request.post?
209 210 unless @project.archive
210 211 flash[:error] = l(:error_can_not_archive_project)
211 212 end
212 213 end
213 214 redirect_to admin_projects_path(:status => params[:status])
214 215 end
215 216
216 217 def unarchive
217 218 @project.unarchive if request.post? && !@project.active?
218 219 redirect_to admin_projects_path(:status => params[:status])
219 220 end
220 221
221 222 def close
222 223 @project.close
223 224 redirect_to project_path(@project)
224 225 end
225 226
226 227 def reopen
227 228 @project.reopen
228 229 redirect_to project_path(@project)
229 230 end
230 231
231 232 # Delete @project
232 233 def destroy
233 234 @project_to_destroy = @project
234 235 if api_request? || params[:confirm]
235 236 @project_to_destroy.destroy
236 237 respond_to do |format|
237 238 format.html { redirect_to admin_projects_path }
238 239 format.api { render_api_ok }
239 240 end
240 241 end
241 242 # hide project in layout
242 243 @project = nil
243 244 end
244 245
245 246 private
246 247
247 248 # Validates parent_id param according to user's permissions
248 249 # TODO: move it to Project model in a validation that depends on User.current
249 250 def validate_parent_id
250 251 return true if User.current.admin?
251 252 parent_id = params[:project] && params[:project][:parent_id]
252 253 if parent_id || @project.new_record?
253 254 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
254 255 unless @project.allowed_parents.include?(parent)
255 256 @project.errors.add :parent_id, :invalid
256 257 return false
257 258 end
258 259 end
259 260 true
260 261 end
261 262 end
@@ -1,27 +1,42
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module GroupsHelper
21 21 def group_settings_tabs
22 22 tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general},
23 23 {:name => 'users', :partial => 'groups/users', :label => :label_user_plural},
24 24 {:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural}
25 25 ]
26 26 end
27
28 def render_principals_for_new_group_users(group)
29 scope = User.active.not_in_group(group).like(params[:q])
30 principal_count = scope.count
31 principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page']
32 principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all
33
34 s = content_tag('div', principals_check_box_tags('user_ids[]', principals), :id => 'principals')
35
36 links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
37 link_to text, autocomplete_for_user_group_path(group, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
38 }
39
40 s + content_tag('p', links, :class => 'pagination')
41 end
27 42 end
@@ -1,21 +1,35
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module MembersHelper
21 def render_principals_for_new_members(project)
22 scope = Principal.active.not_member_of(project).like(params[:q]).order('type, login, lastname ASC')
23 principal_count = scope.count
24 principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page']
25 principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all
26
27 s = content_tag('div', principals_check_box_tags('membership[user_ids][]', principals), :id => 'principals')
28
29 links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
30 link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
31 }
32
33 s + content_tag('p', links, :class => 'pagination')
34 end
21 35 end
@@ -1,43 +1,39
1 1 <div class="splitcontentleft">
2 2 <% if @group.users.any? %>
3 3 <table class="list users">
4 4 <thead><tr>
5 5 <th><%= l(:label_user) %></th>
6 6 <th style="width:15%"></th>
7 7 </tr></thead>
8 8 <tbody>
9 9 <% @group.users.sort.each do |user| %>
10 10 <tr id="user-<%= user.id %>" class="<%= cycle 'odd', 'even' %>">
11 11 <td class="user"><%= link_to_user user %></td>
12 12 <td class="buttons">
13 13 <%= delete_link group_user_path(@group, :user_id => user), :remote => true %>
14 14 </td>
15 15 </tr>
16 16 <% end %>
17 17 </tbody>
18 18 </table>
19 19 <% else %>
20 20 <p class="nodata"><%= l(:label_no_data) %></p>
21 21 <% end %>
22 22 </div>
23 23
24 24 <div class="splitcontentright">
25 <% users = User.active.not_in_group(@group).all(:limit => 100) %>
26 <% if users.any? %>
27 25 <%= form_for(@group, :remote => true, :url => group_users_path(@group),
28 26 :html => {:method => :post}) do |f| %>
29 27 <fieldset><legend><%=l(:label_user_new)%></legend>
30 28
31 29 <p><%= label_tag "user_search", l(:label_user_search) %><%= text_field_tag 'user_search', nil %></p>
32 <%= javascript_tag "observeSearchfield('user_search', 'users', '#{ escape_javascript autocomplete_for_user_group_path(@group) }')" %>
30 <%= javascript_tag "observeSearchfield('user_search', null, '#{ escape_javascript autocomplete_for_user_group_path(@group) }')" %>
33 31
34 32 <div id="users">
35 <%= principals_check_box_tags 'user_ids[]', users %>
33 <%= render_principals_for_new_group_users(@group) %>
36 34 </div>
37 35
38 36 <p><%= submit_tag l(:button_add) %></p>
39 37 </fieldset>
40 38 <% end %>
41 <% end %>
42
43 39 </div>
@@ -1,77 +1,75
1 1 <%= error_messages_for 'member' %>
2 2 <% roles = Role.find_all_givable
3 3 members = @project.member_principals.includes(:roles, :principal).all.sort %>
4 4
5 5 <div class="splitcontentleft">
6 6 <% if members.any? %>
7 7 <table class="list members">
8 8 <thead><tr>
9 9 <th><%= l(:label_user) %> / <%= l(:label_group) %></th>
10 10 <th><%= l(:label_role_plural) %></th>
11 11 <th style="width:15%"></th>
12 12 <%= call_hook(:view_projects_settings_members_table_header, :project => @project) %>
13 13 </tr></thead>
14 14 <tbody>
15 15 <% members.each do |member| %>
16 16 <% next if member.new_record? %>
17 17 <tr id="member-<%= member.id %>" class="<%= cycle 'odd', 'even' %> member">
18 18 <td class="<%= member.principal.class.name.downcase %>"><%= link_to_user member.principal %></td>
19 19 <td class="roles">
20 20 <span id="member-<%= member.id %>-roles"><%=h member.roles.sort.collect(&:to_s).join(', ') %></span>
21 21 <%= form_for(member, {:as => :membership, :remote => true, :url => membership_path(member),
22 22 :method => :put,
23 23 :html => { :id => "member-#{member.id}-roles-form", :class => 'hol' }}
24 24 ) do |f| %>
25 25 <p><% roles.each do |role| %>
26 26 <label><%= check_box_tag 'membership[role_ids][]', role.id, member.roles.include?(role),
27 27 :disabled => member.member_roles.detect {|mr| mr.role_id == role.id && !mr.inherited_from.nil?} %> <%=h role %></label><br />
28 28 <% end %></p>
29 29 <%= hidden_field_tag 'membership[role_ids][]', '' %>
30 30 <p><%= submit_tag l(:button_change), :class => "small" %>
31 31 <%= link_to_function l(:button_cancel),
32 32 "$('#member-#{member.id}-roles').show(); $('#member-#{member.id}-roles-form').hide(); return false;"
33 33 %></p>
34 34 <% end %>
35 35 </td>
36 36 <td class="buttons">
37 37 <%= link_to_function l(:button_edit),
38 38 "$('#member-#{member.id}-roles').hide(); $('#member-#{member.id}-roles-form').show(); return false;",
39 39 :class => 'icon icon-edit' %>
40 40 <%= delete_link membership_path(member),
41 41 :remote => true,
42 42 :data => (!User.current.admin? && member.include?(User.current) ? {:confirm => l(:text_own_membership_delete_confirmation)} : {}) if member.deletable? %>
43 43 </td>
44 44 <%= call_hook(:view_projects_settings_members_table_row, { :project => @project, :member => member}) %>
45 45 </tr>
46 46 <% end; reset_cycle %>
47 47 </tbody>
48 48 </table>
49 49 <% else %>
50 50 <p class="nodata"><%= l(:label_no_data) %></p>
51 51 <% end %>
52 52 </div>
53 53
54 <% principals = Principal.active.not_member_of(@project).all(:limit => 100, :order => 'type, login, lastname ASC') %>
55
56 54 <div class="splitcontentright">
57 <% if roles.any? && principals.any? %>
55 <% if roles.any? %>
58 56 <%= form_for(@member, {:as => :membership, :url => project_memberships_path(@project), :remote => true, :method => :post}) do |f| %>
59 57 <fieldset><legend><%=l(:label_member_new)%></legend>
60 58
61 59 <p><%= label_tag "principal_search", l(:label_principal_search) %><%= text_field_tag 'principal_search', nil %></p>
62 <%= javascript_tag "observeSearchfield('principal_search', 'principals', '#{ escape_javascript autocomplete_project_memberships_path(@project) }')" %>
60 <%= javascript_tag "observeSearchfield('principal_search', null, '#{ escape_javascript autocomplete_project_memberships_path(@project, :format => 'js') }')" %>
63 61
64 <div id="principals">
65 <%= principals_check_box_tags 'membership[user_ids][]', principals %>
62 <div id="principals_for_new_member">
63 <%= render_principals_for_new_members(@project) %>
66 64 </div>
67 65
68 66 <p><%= l(:label_role_plural) %>:
69 67 <% roles.each do |role| %>
70 68 <label><%= check_box_tag 'membership[role_ids][]', role.id %> <%=h role %></label>
71 69 <% end %></p>
72 70
73 71 <p><%= submit_tag l(:button_add), :id => 'member-add-submit' %></p>
74 72 </fieldset>
75 73 <% end %>
76 74 <% end %>
77 75 </div>
@@ -1,590 +1,590
1 1 /* Redmine - project management software
2 2 Copyright (C) 2006-2013 Jean-Philippe Lang */
3 3
4 4 function checkAll(id, checked) {
5 5 if (checked) {
6 6 $('#'+id).find('input[type=checkbox]').attr('checked', true);
7 7 } else {
8 8 $('#'+id).find('input[type=checkbox]').removeAttr('checked');
9 9 }
10 10 }
11 11
12 12 function toggleCheckboxesBySelector(selector) {
13 13 var all_checked = true;
14 14 $(selector).each(function(index) {
15 15 if (!$(this).is(':checked')) { all_checked = false; }
16 16 });
17 17 $(selector).attr('checked', !all_checked)
18 18 }
19 19
20 20 function showAndScrollTo(id, focus) {
21 21 $('#'+id).show();
22 22 if (focus!=null) {
23 23 $('#'+focus).focus();
24 24 }
25 25 $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
26 26 }
27 27
28 28 function toggleRowGroup(el) {
29 29 var tr = $(el).parents('tr').first();
30 30 var n = tr.next();
31 31 tr.toggleClass('open');
32 32 while (n.length && !n.hasClass('group')) {
33 33 n.toggle();
34 34 n = n.next('tr');
35 35 }
36 36 }
37 37
38 38 function collapseAllRowGroups(el) {
39 39 var tbody = $(el).parents('tbody').first();
40 40 tbody.children('tr').each(function(index) {
41 41 if ($(this).hasClass('group')) {
42 42 $(this).removeClass('open');
43 43 } else {
44 44 $(this).hide();
45 45 }
46 46 });
47 47 }
48 48
49 49 function expandAllRowGroups(el) {
50 50 var tbody = $(el).parents('tbody').first();
51 51 tbody.children('tr').each(function(index) {
52 52 if ($(this).hasClass('group')) {
53 53 $(this).addClass('open');
54 54 } else {
55 55 $(this).show();
56 56 }
57 57 });
58 58 }
59 59
60 60 function toggleAllRowGroups(el) {
61 61 var tr = $(el).parents('tr').first();
62 62 if (tr.hasClass('open')) {
63 63 collapseAllRowGroups(el);
64 64 } else {
65 65 expandAllRowGroups(el);
66 66 }
67 67 }
68 68
69 69 function toggleFieldset(el) {
70 70 var fieldset = $(el).parents('fieldset').first();
71 71 fieldset.toggleClass('collapsed');
72 72 fieldset.children('div').toggle();
73 73 }
74 74
75 75 function hideFieldset(el) {
76 76 var fieldset = $(el).parents('fieldset').first();
77 77 fieldset.toggleClass('collapsed');
78 78 fieldset.children('div').hide();
79 79 }
80 80
81 81 function initFilters(){
82 82 $('#add_filter_select').change(function(){
83 83 addFilter($(this).val(), '', []);
84 84 });
85 85 $('#filters-table td.field input[type=checkbox]').each(function(){
86 86 toggleFilter($(this).val());
87 87 });
88 88 $('#filters-table td.field input[type=checkbox]').live('click',function(){
89 89 toggleFilter($(this).val());
90 90 });
91 91 $('#filters-table .toggle-multiselect').live('click',function(){
92 92 toggleMultiSelect($(this).siblings('select'));
93 93 });
94 94 $('#filters-table input[type=text]').live('keypress', function(e){
95 95 if (e.keyCode == 13) submit_query_form("query_form");
96 96 });
97 97 }
98 98
99 99 function addFilter(field, operator, values) {
100 100 var fieldId = field.replace('.', '_');
101 101 var tr = $('#tr_'+fieldId);
102 102 if (tr.length > 0) {
103 103 tr.show();
104 104 } else {
105 105 buildFilterRow(field, operator, values);
106 106 }
107 107 $('#cb_'+fieldId).attr('checked', true);
108 108 toggleFilter(field);
109 109 $('#add_filter_select').val('').children('option').each(function(){
110 110 if ($(this).attr('value') == field) {
111 111 $(this).attr('disabled', true);
112 112 }
113 113 });
114 114 }
115 115
116 116 function buildFilterRow(field, operator, values) {
117 117 var fieldId = field.replace('.', '_');
118 118 var filterTable = $("#filters-table");
119 119 var filterOptions = availableFilters[field];
120 120 var operators = operatorByType[filterOptions['type']];
121 121 var filterValues = filterOptions['values'];
122 122 var i, select;
123 123
124 124 var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html(
125 125 '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' +
126 126 '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' +
127 127 '<td class="values"></td>'
128 128 );
129 129 filterTable.append(tr);
130 130
131 131 select = tr.find('td.operator select');
132 132 for (i=0;i<operators.length;i++){
133 133 var option = $('<option>').val(operators[i]).text(operatorLabels[operators[i]]);
134 134 if (operators[i] == operator) {option.attr('selected', true)};
135 135 select.append(option);
136 136 }
137 137 select.change(function(){toggleOperator(field)});
138 138
139 139 switch (filterOptions['type']){
140 140 case "list":
141 141 case "list_optional":
142 142 case "list_status":
143 143 case "list_subprojects":
144 144 tr.find('td.values').append(
145 145 '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
146 146 ' <span class="toggle-multiselect">&nbsp;</span></span>'
147 147 );
148 148 select = tr.find('td.values select');
149 149 if (values.length > 1) {select.attr('multiple', true)};
150 150 for (i=0;i<filterValues.length;i++){
151 151 var filterValue = filterValues[i];
152 152 var option = $('<option>');
153 153 if ($.isArray(filterValue)) {
154 154 option.val(filterValue[1]).text(filterValue[0]);
155 155 if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);}
156 156 } else {
157 157 option.val(filterValue).text(filterValue);
158 158 if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);}
159 159 }
160 160 select.append(option);
161 161 }
162 162 break;
163 163 case "date":
164 164 case "date_past":
165 165 tr.find('td.values').append(
166 166 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' +
167 167 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' +
168 168 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>'
169 169 );
170 170 $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions);
171 171 $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions);
172 172 $('#values_'+fieldId).val(values[0]);
173 173 break;
174 174 case "string":
175 175 case "text":
176 176 tr.find('td.values').append(
177 177 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>'
178 178 );
179 179 $('#values_'+fieldId).val(values[0]);
180 180 break;
181 181 case "relation":
182 182 tr.find('td.values').append(
183 183 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="6" class="value" /></span>' +
184 184 '<span style="display:none;"><select class="value" name="v['+field+'][]" id="values_'+fieldId+'_1"></select></span>'
185 185 );
186 186 $('#values_'+fieldId).val(values[0]);
187 187 select = tr.find('td.values select');
188 188 for (i=0;i<allProjects.length;i++){
189 189 var filterValue = allProjects[i];
190 190 var option = $('<option>');
191 191 option.val(filterValue[1]).text(filterValue[0]);
192 192 if (values[0] == filterValue[1]) {option.attr('selected', true)};
193 193 select.append(option);
194 194 }
195 195 case "integer":
196 196 case "float":
197 197 tr.find('td.values').append(
198 198 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" /></span>' +
199 199 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" /></span>'
200 200 );
201 201 $('#values_'+fieldId+'_1').val(values[0]);
202 202 $('#values_'+fieldId+'_2').val(values[1]);
203 203 break;
204 204 }
205 205 }
206 206
207 207 function toggleFilter(field) {
208 208 var fieldId = field.replace('.', '_');
209 209 if ($('#cb_' + fieldId).is(':checked')) {
210 210 $("#operators_" + fieldId).show().removeAttr('disabled');
211 211 toggleOperator(field);
212 212 } else {
213 213 $("#operators_" + fieldId).hide().attr('disabled', true);
214 214 enableValues(field, []);
215 215 }
216 216 }
217 217
218 218 function enableValues(field, indexes) {
219 219 var fieldId = field.replace('.', '_');
220 220 $('#tr_'+fieldId+' td.values .value').each(function(index) {
221 221 if ($.inArray(index, indexes) >= 0) {
222 222 $(this).removeAttr('disabled');
223 223 $(this).parents('span').first().show();
224 224 } else {
225 225 $(this).val('');
226 226 $(this).attr('disabled', true);
227 227 $(this).parents('span').first().hide();
228 228 }
229 229
230 230 if ($(this).hasClass('group')) {
231 231 $(this).addClass('open');
232 232 } else {
233 233 $(this).show();
234 234 }
235 235 });
236 236 }
237 237
238 238 function toggleOperator(field) {
239 239 var fieldId = field.replace('.', '_');
240 240 var operator = $("#operators_" + fieldId);
241 241 switch (operator.val()) {
242 242 case "!*":
243 243 case "*":
244 244 case "t":
245 245 case "ld":
246 246 case "w":
247 247 case "lw":
248 248 case "l2w":
249 249 case "m":
250 250 case "lm":
251 251 case "y":
252 252 case "o":
253 253 case "c":
254 254 enableValues(field, []);
255 255 break;
256 256 case "><":
257 257 enableValues(field, [0,1]);
258 258 break;
259 259 case "<t+":
260 260 case ">t+":
261 261 case "><t+":
262 262 case "t+":
263 263 case ">t-":
264 264 case "<t-":
265 265 case "><t-":
266 266 case "t-":
267 267 enableValues(field, [2]);
268 268 break;
269 269 case "=p":
270 270 case "=!p":
271 271 case "!p":
272 272 enableValues(field, [1]);
273 273 break;
274 274 default:
275 275 enableValues(field, [0]);
276 276 break;
277 277 }
278 278 }
279 279
280 280 function toggleMultiSelect(el) {
281 281 if (el.attr('multiple')) {
282 282 el.removeAttr('multiple');
283 283 } else {
284 284 el.attr('multiple', true);
285 285 }
286 286 }
287 287
288 288 function submit_query_form(id) {
289 289 selectAllOptions("selected_columns");
290 290 $('#'+id).submit();
291 291 }
292 292
293 293 function showTab(name) {
294 294 $('div#content .tab-content').hide();
295 295 $('div.tabs a').removeClass('selected');
296 296 $('#tab-content-' + name).show();
297 297 $('#tab-' + name).addClass('selected');
298 298 return false;
299 299 }
300 300
301 301 function moveTabRight(el) {
302 302 var lis = $(el).parents('div.tabs').first().find('ul').children();
303 303 var tabsWidth = 0;
304 304 var i = 0;
305 305 lis.each(function(){
306 306 if ($(this).is(':visible')) {
307 307 tabsWidth += $(this).width() + 6;
308 308 }
309 309 });
310 310 if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; }
311 311 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
312 312 lis.eq(i).hide();
313 313 }
314 314
315 315 function moveTabLeft(el) {
316 316 var lis = $(el).parents('div.tabs').first().find('ul').children();
317 317 var i = 0;
318 318 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
319 319 if (i>0) {
320 320 lis.eq(i-1).show();
321 321 }
322 322 }
323 323
324 324 function displayTabsButtons() {
325 325 var lis;
326 326 var tabsWidth = 0;
327 327 var el;
328 328 $('div.tabs').each(function() {
329 329 el = $(this);
330 330 lis = el.find('ul').children();
331 331 lis.each(function(){
332 332 if ($(this).is(':visible')) {
333 333 tabsWidth += $(this).width() + 6;
334 334 }
335 335 });
336 336 if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) {
337 337 el.find('div.tabs-buttons').hide();
338 338 } else {
339 339 el.find('div.tabs-buttons').show();
340 340 }
341 341 });
342 342 }
343 343
344 344 function setPredecessorFieldsVisibility() {
345 345 var relationType = $('#relation_relation_type');
346 346 if (relationType.val() == "precedes" || relationType.val() == "follows") {
347 347 $('#predecessor_fields').show();
348 348 } else {
349 349 $('#predecessor_fields').hide();
350 350 }
351 351 }
352 352
353 353 function showModal(id, width) {
354 354 var el = $('#'+id).first();
355 355 if (el.length == 0 || el.is(':visible')) {return;}
356 356 var title = el.find('h3.title').text();
357 357 el.dialog({
358 358 width: width,
359 359 modal: true,
360 360 resizable: false,
361 361 dialogClass: 'modal',
362 362 title: title
363 363 });
364 364 el.find("input[type=text], input[type=submit]").first().focus();
365 365 }
366 366
367 367 function hideModal(el) {
368 368 var modal;
369 369 if (el) {
370 370 modal = $(el).parents('.ui-dialog-content');
371 371 } else {
372 372 modal = $('#ajax-modal');
373 373 }
374 374 modal.dialog("close");
375 375 }
376 376
377 377 function submitPreview(url, form, target) {
378 378 $.ajax({
379 379 url: url,
380 380 type: 'post',
381 381 data: $('#'+form).serialize(),
382 382 success: function(data){
383 383 $('#'+target).html(data);
384 384 }
385 385 });
386 386 }
387 387
388 388 function collapseScmEntry(id) {
389 389 $('.'+id).each(function() {
390 390 if ($(this).hasClass('open')) {
391 391 collapseScmEntry($(this).attr('id'));
392 392 }
393 393 $(this).hide();
394 394 });
395 395 $('#'+id).removeClass('open');
396 396 }
397 397
398 398 function expandScmEntry(id) {
399 399 $('.'+id).each(function() {
400 400 $(this).show();
401 401 if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
402 402 expandScmEntry($(this).attr('id'));
403 403 }
404 404 });
405 405 $('#'+id).addClass('open');
406 406 }
407 407
408 408 function scmEntryClick(id, url) {
409 409 el = $('#'+id);
410 410 if (el.hasClass('open')) {
411 411 collapseScmEntry(id);
412 412 el.addClass('collapsed');
413 413 return false;
414 414 } else if (el.hasClass('loaded')) {
415 415 expandScmEntry(id);
416 416 el.removeClass('collapsed');
417 417 return false;
418 418 }
419 419 if (el.hasClass('loading')) {
420 420 return false;
421 421 }
422 422 el.addClass('loading');
423 423 $.ajax({
424 424 url: url,
425 425 success: function(data){
426 426 el.after(data);
427 427 el.addClass('open').addClass('loaded').removeClass('loading');
428 428 }
429 429 });
430 430 return true;
431 431 }
432 432
433 433 function randomKey(size) {
434 434 var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
435 435 var key = '';
436 436 for (i = 0; i < size; i++) {
437 437 key += chars[Math.floor(Math.random() * chars.length)];
438 438 }
439 439 return key;
440 440 }
441 441
442 442 // Can't use Rails' remote select because we need the form data
443 443 function updateIssueFrom(url) {
444 444 $.ajax({
445 445 url: url,
446 446 type: 'post',
447 447 data: $('#issue-form').serialize()
448 448 });
449 449 }
450 450
451 451 function updateBulkEditFrom(url) {
452 452 $.ajax({
453 453 url: url,
454 454 type: 'post',
455 455 data: $('#bulk_edit_form').serialize()
456 456 });
457 457 }
458 458
459 459 function observeAutocompleteField(fieldId, url, options) {
460 460 $(document).ready(function() {
461 461 $('#'+fieldId).autocomplete($.extend({
462 462 source: url,
463 463 minLength: 2,
464 464 search: function(){$('#'+fieldId).addClass('ajax-loading');},
465 465 response: function(){$('#'+fieldId).removeClass('ajax-loading');},
466 466 }, options));
467 467 $('#'+fieldId).addClass('autocomplete');
468 468 });
469 469 }
470 470
471 471 function observeSearchfield(fieldId, targetId, url) {
472 472 $('#'+fieldId).each(function() {
473 473 var $this = $(this);
474 474 $this.addClass('autocomplete');
475 475 $this.attr('data-value-was', $this.val());
476 476 var check = function() {
477 477 var val = $this.val();
478 478 if ($this.attr('data-value-was') != val){
479 479 $this.attr('data-value-was', val);
480 480 $.ajax({
481 481 url: url,
482 482 type: 'get',
483 483 data: {q: $this.val()},
484 success: function(data){ $('#'+targetId).html(data); },
484 success: function(data){ if(targetId) $('#'+targetId).html(data); },
485 485 beforeSend: function(){ $this.addClass('ajax-loading'); },
486 486 complete: function(){ $this.removeClass('ajax-loading'); }
487 487 });
488 488 }
489 489 };
490 490 var reset = function() {
491 491 if (timer) {
492 492 clearInterval(timer);
493 493 timer = setInterval(check, 300);
494 494 }
495 495 };
496 496 var timer = setInterval(check, 300);
497 497 $this.bind('keyup click mousemove', reset);
498 498 });
499 499 }
500 500
501 501 function observeProjectModules() {
502 502 var f = function() {
503 503 /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */
504 504 if ($('#project_enabled_module_names_issue_tracking').attr('checked')) {
505 505 $('#project_trackers').show();
506 506 }else{
507 507 $('#project_trackers').hide();
508 508 }
509 509 };
510 510
511 511 $(window).load(f);
512 512 $('#project_enabled_module_names_issue_tracking').change(f);
513 513 }
514 514
515 515 function initMyPageSortable(list, url) {
516 516 $('#list-'+list).sortable({
517 517 connectWith: '.block-receiver',
518 518 tolerance: 'pointer',
519 519 update: function(){
520 520 $.ajax({
521 521 url: url,
522 522 type: 'post',
523 523 data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})}
524 524 });
525 525 }
526 526 });
527 527 $("#list-top, #list-left, #list-right").disableSelection();
528 528 }
529 529
530 530 var warnLeavingUnsavedMessage;
531 531 function warnLeavingUnsaved(message) {
532 532 warnLeavingUnsavedMessage = message;
533 533
534 534 $('form').submit(function(){
535 535 $('textarea').removeData('changed');
536 536 });
537 537 $('textarea').change(function(){
538 538 $(this).data('changed', 'changed');
539 539 });
540 540 window.onbeforeunload = function(){
541 541 var warn = false;
542 542 $('textarea').blur().each(function(){
543 543 if ($(this).data('changed')) {
544 544 warn = true;
545 545 }
546 546 });
547 547 if (warn) {return warnLeavingUnsavedMessage;}
548 548 };
549 549 };
550 550
551 551 function setupAjaxIndicator() {
552 552
553 553 $('#ajax-indicator').bind('ajaxSend', function(event, xhr, settings) {
554 554
555 555 if ($('.ajax-loading').length == 0 && settings.contentType != 'application/octet-stream') {
556 556 $('#ajax-indicator').show();
557 557 }
558 558 });
559 559
560 560 $('#ajax-indicator').bind('ajaxStop', function() {
561 561 $('#ajax-indicator').hide();
562 562 });
563 563 }
564 564
565 565 function hideOnLoad() {
566 566 $('.hol').hide();
567 567 }
568 568
569 569 function addFormObserversForDoubleSubmit() {
570 570 $('form[method=post]').each(function() {
571 571 if (!$(this).hasClass('multiple-submit')) {
572 572 $(this).submit(function(form_submission) {
573 573 if ($(form_submission.target).attr('data-submitted')) {
574 574 form_submission.preventDefault();
575 575 } else {
576 576 $(form_submission.target).attr('data-submitted', true);
577 577 }
578 578 });
579 579 }
580 580 });
581 581 }
582 582
583 583 function blockEventPropagation(event) {
584 584 event.stopPropagation();
585 585 event.preventDefault();
586 586 }
587 587
588 588 $(document).ready(setupAjaxIndicator);
589 589 $(document).ready(hideOnLoad);
590 590 $(document).ready(addFormObserversForDoubleSubmit);
@@ -1,1147 +1,1147
1 1 html {overflow-y:scroll;}
2 2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
3 3
4 4 h1, h2, h3, h4 {font-family: "Trebuchet MS", Verdana, sans-serif;padding: 2px 10px 1px 0px;margin: 0 0 10px 0;}
5 5 #content h1, h2, h3, h4 {color: #555;}
6 6 h2, .wiki h1 {font-size: 20px;}
7 7 h3, .wiki h2 {font-size: 16px;}
8 8 h4, .wiki h3 {font-size: 13px;}
9 9 h4 {border-bottom: 1px dotted #bbb;}
10 10
11 11 /***** Layout *****/
12 12 #wrapper {background: white;}
13 13
14 14 #top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
15 15 #top-menu ul {margin: 0; padding: 0;}
16 16 #top-menu li {
17 17 float:left;
18 18 list-style-type:none;
19 19 margin: 0px 0px 0px 0px;
20 20 padding: 0px 0px 0px 0px;
21 21 white-space:nowrap;
22 22 }
23 23 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
24 24 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
25 25
26 26 #account {float:right;}
27 27
28 28 #header {height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
29 29 #header a {color:#f8f8f8;}
30 30 #header h1 a.ancestor { font-size: 80%; }
31 31 #quick-search {float:right;}
32 32
33 33 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
34 34 #main-menu ul {margin: 0; padding: 0;}
35 35 #main-menu li {
36 36 float:left;
37 37 list-style-type:none;
38 38 margin: 0px 2px 0px 0px;
39 39 padding: 0px 0px 0px 0px;
40 40 white-space:nowrap;
41 41 }
42 42 #main-menu li a {
43 43 display: block;
44 44 color: #fff;
45 45 text-decoration: none;
46 46 font-weight: bold;
47 47 margin: 0;
48 48 padding: 4px 10px 4px 10px;
49 49 }
50 50 #main-menu li a:hover {background:#759FCF; color:#fff;}
51 51 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
52 52
53 53 #admin-menu ul {margin: 0; padding: 0;}
54 54 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
55 55
56 56 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
57 57 #admin-menu a.projects { background-image: url(../images/projects.png); }
58 58 #admin-menu a.users { background-image: url(../images/user.png); }
59 59 #admin-menu a.groups { background-image: url(../images/group.png); }
60 60 #admin-menu a.roles { background-image: url(../images/database_key.png); }
61 61 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
62 62 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
63 63 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
64 64 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
65 65 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
66 66 #admin-menu a.settings { background-image: url(../images/changeset.png); }
67 67 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
68 68 #admin-menu a.info { background-image: url(../images/help.png); }
69 69 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
70 70
71 71 #main {background-color:#EEEEEE;}
72 72
73 73 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
74 74 * html #sidebar{ width: 22%; }
75 75 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
76 76 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
77 77 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
78 78 #sidebar .contextual { margin-right: 1em; }
79 79
80 80 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
81 81 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
82 82 html>body #content { min-height: 600px; }
83 83 * html body #content { height: 600px; } /* IE */
84 84
85 85 #main.nosidebar #sidebar{ display: none; }
86 86 #main.nosidebar #content{ width: auto; border-right: 0; }
87 87
88 88 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
89 89
90 90 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
91 91 #login-form table td {padding: 6px;}
92 92 #login-form label {font-weight: bold;}
93 93 #login-form input#username, #login-form input#password { width: 300px; }
94 94
95 95 div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
96 96 div.modal h3.title {display:none;}
97 97 div.modal p.buttons {text-align:right; margin-bottom:0;}
98 98
99 99 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
100 100
101 101 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
102 102
103 103 /***** Links *****/
104 104 a, a:link, a:visited{ color: #169; text-decoration: none; }
105 105 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
106 106 a img{ border: 0; }
107 107
108 108 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
109 109 a.project.closed, a.project.closed:link, a.project.closed:visited { color: #999; }
110 110 a.user.locked, a.user.locked:link, a.user.locked:visited {color: #999;}
111 111
112 112 #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;}
113 113 #sidebar a.selected:hover {text-decoration:none;}
114 114 #admin-menu a {line-height:1.7em;}
115 115 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
116 116
117 117 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
118 118 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
119 119
120 120 a#toggle-completed-versions {color:#999;}
121 121 /***** Tables *****/
122 122 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
123 123 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
124 124 table.list td { vertical-align: top; padding-right:10px; }
125 125 table.list td.id { width: 2%; text-align: center;}
126 126 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
127 127 table.list td.checkbox input {padding:0px;}
128 128 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
129 129 table.list td.buttons a { padding-right: 0.6em; }
130 130 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
131 131
132 132 tr.project td.name a { white-space:nowrap; }
133 133 tr.project.closed, tr.project.archived { color: #aaa; }
134 134 tr.project.closed a, tr.project.archived a { color: #aaa; }
135 135
136 136 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
137 137 tr.project.idnt-1 td.name {padding-left: 0.5em;}
138 138 tr.project.idnt-2 td.name {padding-left: 2em;}
139 139 tr.project.idnt-3 td.name {padding-left: 3.5em;}
140 140 tr.project.idnt-4 td.name {padding-left: 5em;}
141 141 tr.project.idnt-5 td.name {padding-left: 6.5em;}
142 142 tr.project.idnt-6 td.name {padding-left: 8em;}
143 143 tr.project.idnt-7 td.name {padding-left: 9.5em;}
144 144 tr.project.idnt-8 td.name {padding-left: 11em;}
145 145 tr.project.idnt-9 td.name {padding-left: 12.5em;}
146 146
147 147 tr.issue { text-align: center; white-space: nowrap; }
148 148 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text, tr.issue td.relations { white-space: normal; }
149 149 tr.issue td.subject, tr.issue td.relations { text-align: left; }
150 150 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
151 151 tr.issue td.relations span {white-space: nowrap;}
152 152 table.issues td.description {color:#777; font-size:90%; padding:4px 4px 4px 24px; text-align:left; white-space:normal;}
153 153 table.issues td.description pre {white-space:normal;}
154 154
155 155 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
156 156 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
157 157 tr.issue.idnt-2 td.subject {padding-left: 2em;}
158 158 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
159 159 tr.issue.idnt-4 td.subject {padding-left: 5em;}
160 160 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
161 161 tr.issue.idnt-6 td.subject {padding-left: 8em;}
162 162 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
163 163 tr.issue.idnt-8 td.subject {padding-left: 11em;}
164 164 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
165 165
166 166 tr.entry { border: 1px solid #f8f8f8; }
167 167 tr.entry td { white-space: nowrap; }
168 168 tr.entry td.filename { width: 30%; }
169 169 tr.entry td.filename_no_report { width: 70%; }
170 170 tr.entry td.size { text-align: right; font-size: 90%; }
171 171 tr.entry td.revision, tr.entry td.author { text-align: center; }
172 172 tr.entry td.age { text-align: right; }
173 173 tr.entry.file td.filename a { margin-left: 16px; }
174 174 tr.entry.file td.filename_no_report a { margin-left: 16px; }
175 175
176 176 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
177 177 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
178 178
179 179 tr.changeset { height: 20px }
180 180 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
181 181 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
182 182 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
183 183 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
184 184
185 185 table.files tr.file td { text-align: center; }
186 186 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
187 187 table.files tr.file td.digest { font-size: 80%; }
188 188
189 189 table.members td.roles, table.memberships td.roles { width: 45%; }
190 190
191 191 tr.message { height: 2.6em; }
192 192 tr.message td.subject { padding-left: 20px; }
193 193 tr.message td.created_on { white-space: nowrap; }
194 194 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
195 195 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
196 196 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
197 197
198 198 tr.version.closed, tr.version.closed a { color: #999; }
199 199 tr.version td.name { padding-left: 20px; }
200 200 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
201 201 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
202 202
203 203 tr.user td { width:13%; }
204 204 tr.user td.email { width:18%; }
205 205 tr.user td { white-space: nowrap; }
206 206 tr.user.locked, tr.user.registered { color: #aaa; }
207 207 tr.user.locked a, tr.user.registered a { color: #aaa; }
208 208
209 209 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
210 210
211 211 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
212 212
213 213 tr.time-entry { text-align: center; white-space: nowrap; }
214 214 tr.time-entry td.issue, tr.time-entry td.comments { text-align: left; white-space: normal; }
215 215 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
216 216 td.hours .hours-dec { font-size: 0.9em; }
217 217
218 218 table.plugins td { vertical-align: middle; }
219 219 table.plugins td.configure { text-align: right; padding-right: 1em; }
220 220 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
221 221 table.plugins span.description { display: block; font-size: 0.9em; }
222 222 table.plugins span.url { display: block; font-size: 0.9em; }
223 223
224 224 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
225 225 table.list tbody tr.group span.count {position:relative; top:-1px; color:#fff; font-size:10px; background:#9DB9D5; padding:0px 6px 1px 6px; border-radius:3px; margin-left:4px;}
226 226 tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;}
227 227 tr.group:hover a.toggle-all { display:inline;}
228 228 a.toggle-all:hover {text-decoration:none;}
229 229
230 230 table.list tbody tr:hover { background-color:#ffffdd; }
231 231 table.list tbody tr.group:hover { background-color:inherit; }
232 232 table td {padding:2px;}
233 233 table p {margin:0;}
234 234 .odd {background-color:#f6f7f8;}
235 235 .even {background-color: #fff;}
236 236
237 237 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
238 238 a.sort.asc { background-image: url(../images/sort_asc.png); }
239 239 a.sort.desc { background-image: url(../images/sort_desc.png); }
240 240
241 241 table.attributes { width: 100% }
242 242 table.attributes th { vertical-align: top; text-align: left; }
243 243 table.attributes td { vertical-align: top; }
244 244
245 245 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
246 246 table.boards td.topic-count, table.boards td.message-count {text-align:center;}
247 247 table.boards td.last-message {font-size:80%;}
248 248
249 249 table.messages td.author, table.messages td.created_on, table.messages td.reply-count {text-align:center;}
250 250
251 251 table.query-columns {
252 252 border-collapse: collapse;
253 253 border: 0;
254 254 }
255 255
256 256 table.query-columns td.buttons {
257 257 vertical-align: middle;
258 258 text-align: center;
259 259 }
260 260
261 261 td.center {text-align:center;}
262 262
263 263 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
264 264
265 265 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
266 266 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
267 267 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
268 268 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
269 269
270 270 #watchers ul {margin: 0; padding: 0;}
271 271 #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
272 272 #watchers select {width: 95%; display: block;}
273 273 #watchers a.delete {opacity: 0.4;}
274 274 #watchers a.delete:hover {opacity: 1;}
275 275 #watchers img.gravatar {margin: 0 4px 2px 0;}
276 276
277 277 span#watchers_inputs {overflow:auto; display:block;}
278 278 span.search_for_watchers {display:block;}
279 279 span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
280 280 span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
281 281
282 282
283 283 .highlight { background-color: #FCFD8D;}
284 284 .highlight.token-1 { background-color: #faa;}
285 285 .highlight.token-2 { background-color: #afa;}
286 286 .highlight.token-3 { background-color: #aaf;}
287 287
288 288 .box{
289 289 padding:6px;
290 290 margin-bottom: 10px;
291 291 background-color:#f6f6f6;
292 292 color:#505050;
293 293 line-height:1.5em;
294 294 border: 1px solid #e4e4e4;
295 295 }
296 296
297 297 div.square {
298 298 border: 1px solid #999;
299 299 float: left;
300 300 margin: .3em .4em 0 .4em;
301 301 overflow: hidden;
302 302 width: .6em; height: .6em;
303 303 }
304 304 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
305 305 .contextual input, .contextual select {font-size:0.9em;}
306 306 .message .contextual { margin-top: 0; }
307 307
308 308 .splitcontent {overflow:auto;}
309 309 .splitcontentleft{float:left; width:49%;}
310 310 .splitcontentright{float:right; width:49%;}
311 311 form {display: inline;}
312 312 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
313 313 fieldset {border: 1px solid #e4e4e4; margin:0;}
314 314 legend {color: #484848;}
315 315 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
316 316 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
317 317 blockquote blockquote { margin-left: 0;}
318 318 acronym { border-bottom: 1px dotted; cursor: help; }
319 319 textarea.wiki-edit {width:99%; resize:vertical;}
320 320 li p {margin-top: 0;}
321 321 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
322 322 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
323 323 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
324 324 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
325 325
326 326 div.issue div.subject div div { padding-left: 16px; }
327 327 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
328 328 div.issue div.subject>div>p { margin-top: 0.5em; }
329 329 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
330 330 div.issue span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px;}
331 331 div.issue .next-prev-links {color:#999;}
332 332 div.issue table.attributes th {width:22%;}
333 333 div.issue table.attributes td {width:28%;}
334 334
335 335 #issue_tree table.issues, #relations table.issues { border: 0; }
336 336 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
337 337 #relations td.buttons {padding:0;}
338 338
339 339 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
340 340 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
341 341 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
342 342
343 343 fieldset#date-range p { margin: 2px 0 2px 0; }
344 344 fieldset#filters table { border-collapse: collapse; }
345 345 fieldset#filters table td { padding: 0; vertical-align: middle; }
346 346 fieldset#filters tr.filter { height: 2.1em; }
347 347 fieldset#filters td.field { width:230px; }
348 348 fieldset#filters td.operator { width:180px; }
349 349 fieldset#filters td.operator select {max-width:170px;}
350 350 fieldset#filters td.values { white-space:nowrap; }
351 351 fieldset#filters td.values select {min-width:130px;}
352 352 fieldset#filters td.values input {height:1em;}
353 353 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
354 354
355 355 .toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;}
356 356 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
357 357
358 358 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
359 359 div#issue-changesets div.changeset { padding: 4px;}
360 360 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
361 361 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
362 362
363 363 .journal ul.details img {margin:0 0 -3px 4px;}
364 364 div.journal {overflow:auto;}
365 365 div.journal.private-notes {border-left:2px solid #d22; padding-left:4px; margin-left:-6px;}
366 366
367 367 div#activity dl, #search-results { margin-left: 2em; }
368 368 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
369 369 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
370 370 div#activity dt.me .time { border-bottom: 1px solid #999; }
371 371 div#activity dt .time { color: #777; font-size: 80%; }
372 372 div#activity dd .description, #search-results dd .description { font-style: italic; }
373 373 div#activity span.project:after, #search-results span.project:after { content: " -"; }
374 374 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
375 375 div#activity dt.grouped {margin-left:5em;}
376 376 div#activity dd.grouped {margin-left:9em;}
377 377
378 378 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
379 379
380 380 div#search-results-counts {float:right;}
381 381 div#search-results-counts ul { margin-top: 0.5em; }
382 382 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
383 383
384 384 dt.issue { background-image: url(../images/ticket.png); }
385 385 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
386 386 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
387 387 dt.issue-note { background-image: url(../images/ticket_note.png); }
388 388 dt.changeset { background-image: url(../images/changeset.png); }
389 389 dt.news { background-image: url(../images/news.png); }
390 390 dt.message { background-image: url(../images/message.png); }
391 391 dt.reply { background-image: url(../images/comments.png); }
392 392 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
393 393 dt.attachment { background-image: url(../images/attachment.png); }
394 394 dt.document { background-image: url(../images/document.png); }
395 395 dt.project { background-image: url(../images/projects.png); }
396 396 dt.time-entry { background-image: url(../images/time.png); }
397 397
398 398 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
399 399
400 400 div#roadmap .related-issues { margin-bottom: 1em; }
401 401 div#roadmap .related-issues td.checkbox { display: none; }
402 402 div#roadmap .wiki h1:first-child { display: none; }
403 403 div#roadmap .wiki h1 { font-size: 120%; }
404 404 div#roadmap .wiki h2 { font-size: 110%; }
405 405 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
406 406
407 407 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
408 408 div#version-summary fieldset { margin-bottom: 1em; }
409 409 div#version-summary fieldset.time-tracking table { width:100%; }
410 410 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
411 411
412 412 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
413 413 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
414 414 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
415 415 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
416 416 table#time-report .hours-dec { font-size: 0.9em; }
417 417
418 418 div.wiki-page .contextual a {opacity: 0.4}
419 419 div.wiki-page .contextual a:hover {opacity: 1}
420 420
421 421 form .attributes select { width: 60%; }
422 422 input#issue_subject { width: 99%; }
423 423 select#issue_done_ratio { width: 95px; }
424 424
425 425 ul.projects {margin:0; padding-left:1em;}
426 426 ul.projects ul {padding-left:1.6em;}
427 427 ul.projects.root {margin:0; padding:0;}
428 428 ul.projects li {list-style-type:none;}
429 429
430 430 #projects-index ul.projects ul.projects { border-left: 3px solid #e0e0e0; padding-left:1em;}
431 431 #projects-index ul.projects li.root {margin-bottom: 1em;}
432 432 #projects-index ul.projects li.child {margin-top: 1em;}
433 433 #projects-index ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
434 434 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
435 435
436 436 #notified-projects ul, #tracker_project_ids ul {max-height:250px; overflow-y:auto;}
437 437
438 438 #related-issues li img {vertical-align:middle;}
439 439
440 440 ul.properties {padding:0; font-size: 0.9em; color: #777;}
441 441 ul.properties li {list-style-type:none;}
442 442 ul.properties li span {font-style:italic;}
443 443
444 444 .total-hours { font-size: 110%; font-weight: bold; }
445 445 .total-hours span.hours-int { font-size: 120%; }
446 446
447 447 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
448 448 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
449 449
450 450 #workflow_copy_form select { width: 200px; }
451 451 table.transitions td.enabled {background: #bfb;}
452 452 table.fields_permissions select {font-size:90%}
453 453 table.fields_permissions td.readonly {background:#ddd;}
454 454 table.fields_permissions td.required {background:#d88;}
455 455
456 456 textarea#custom_field_possible_values {width: 99%}
457 457 input#content_comments {width: 99%}
458 458
459 459 p.pagination {margin-top:8px; font-size: 90%}
460 460
461 461 /***** Tabular forms ******/
462 462 .tabular p{
463 463 margin: 0;
464 464 padding: 3px 0 3px 0;
465 465 padding-left: 180px; /* width of left column containing the label elements */
466 466 min-height: 1.8em;
467 467 clear:left;
468 468 }
469 469
470 470 html>body .tabular p {overflow:hidden;}
471 471
472 472 .tabular label{
473 473 font-weight: bold;
474 474 float: left;
475 475 text-align: right;
476 476 /* width of left column */
477 477 margin-left: -180px;
478 478 /* width of labels. Should be smaller than left column to create some right margin */
479 479 width: 175px;
480 480 }
481 481
482 482 .tabular label.floating{
483 483 font-weight: normal;
484 484 margin-left: 0px;
485 485 text-align: left;
486 486 width: 270px;
487 487 }
488 488
489 489 .tabular label.block{
490 490 font-weight: normal;
491 491 margin-left: 0px !important;
492 492 text-align: left;
493 493 float: none;
494 494 display: block;
495 495 width: auto;
496 496 }
497 497
498 498 .tabular label.inline{
499 499 font-weight: normal;
500 500 float:none;
501 501 margin-left: 5px !important;
502 502 width: auto;
503 503 }
504 504
505 505 label.no-css {
506 506 font-weight: inherit;
507 507 float:none;
508 508 text-align:left;
509 509 margin-left:0px;
510 510 width:auto;
511 511 }
512 512 input#time_entry_comments { width: 90%;}
513 513
514 514 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
515 515
516 516 .tabular.settings p{ padding-left: 300px; }
517 517 .tabular.settings label{ margin-left: -300px; width: 295px; }
518 518 .tabular.settings textarea { width: 99%; }
519 519
520 520 .settings.enabled_scm table {width:100%}
521 521 .settings.enabled_scm td.scm_name{ font-weight: bold; }
522 522
523 523 fieldset.settings label { display: block; }
524 524 fieldset#notified_events .parent { padding-left: 20px; }
525 525
526 526 span.required {color: #bb0000;}
527 527 .summary {font-style: italic;}
528 528
529 529 #attachments_fields input.description {margin-left:4px; width:340px;}
530 530 #attachments_fields span {display:block; white-space:nowrap;}
531 531 #attachments_fields input.filename {border:0; height:1.8em; width:250px; color:#555; background-color:inherit; background:url(../images/attachment.png) no-repeat 1px 50%; padding-left:18px;}
532 532 #attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;}
533 533 #attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;}
534 534 #attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; }
535 535 a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;}
536 536 a.remove-upload:hover {text-decoration:none !important;}
537 537
538 538 div.fileover { background-color: lavender; }
539 539
540 540 div.attachments { margin-top: 12px; }
541 541 div.attachments p { margin:4px 0 2px 0; }
542 542 div.attachments img { vertical-align: middle; }
543 543 div.attachments span.author { font-size: 0.9em; color: #888; }
544 544
545 545 div.thumbnails {margin-top:0.6em;}
546 546 div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;}
547 547 div.thumbnails img {margin: 3px;}
548 548
549 549 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
550 550 .other-formats span + span:before { content: "| "; }
551 551
552 552 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
553 553
554 554 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
555 555 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
556 556
557 557 textarea.text_cf {width:90%;}
558 558
559 559 /* Project members tab */
560 560 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
561 561 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
562 562 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
563 563 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
564 564 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
565 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
565 div#tab-content-members #principals, div#tab-content-users #principals { max-height: 400px; overflow:auto; }
566 566
567 567 #users_for_watcher {height: 200px; overflow:auto;}
568 568 #users_for_watcher label {display: block;}
569 569
570 570 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
571 571
572 572 input#principal_search, input#user_search {width:90%}
573 573
574 574 input.autocomplete {
575 575 background: #fff url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px;
576 576 border:1px solid #9EB1C2; border-radius:2px; height:1.5em;
577 577 }
578 578 input.autocomplete.ajax-loading {
579 579 background-image: url(../images/loading.gif);
580 580 }
581 581
582 582 * html div#tab-content-members fieldset div { height: 450px; }
583 583
584 584 /***** Flash & error messages ****/
585 585 #errorExplanation, div.flash, .nodata, .warning, .conflict {
586 586 padding: 4px 4px 4px 30px;
587 587 margin-bottom: 12px;
588 588 font-size: 1.1em;
589 589 border: 2px solid;
590 590 }
591 591
592 592 div.flash {margin-top: 8px;}
593 593
594 594 div.flash.error, #errorExplanation {
595 595 background: url(../images/exclamation.png) 8px 50% no-repeat;
596 596 background-color: #ffe3e3;
597 597 border-color: #dd0000;
598 598 color: #880000;
599 599 }
600 600
601 601 div.flash.notice {
602 602 background: url(../images/true.png) 8px 5px no-repeat;
603 603 background-color: #dfffdf;
604 604 border-color: #9fcf9f;
605 605 color: #005f00;
606 606 }
607 607
608 608 div.flash.warning, .conflict {
609 609 background: url(../images/warning.png) 8px 5px no-repeat;
610 610 background-color: #FFEBC1;
611 611 border-color: #FDBF3B;
612 612 color: #A6750C;
613 613 text-align: left;
614 614 }
615 615
616 616 .nodata, .warning {
617 617 text-align: center;
618 618 background-color: #FFEBC1;
619 619 border-color: #FDBF3B;
620 620 color: #A6750C;
621 621 }
622 622
623 623 #errorExplanation ul { font-size: 0.9em;}
624 624 #errorExplanation h2, #errorExplanation p { display: none; }
625 625
626 626 .conflict-details {font-size:80%;}
627 627
628 628 /***** Ajax indicator ******/
629 629 #ajax-indicator {
630 630 position: absolute; /* fixed not supported by IE */
631 631 background-color:#eee;
632 632 border: 1px solid #bbb;
633 633 top:35%;
634 634 left:40%;
635 635 width:20%;
636 636 font-weight:bold;
637 637 text-align:center;
638 638 padding:0.6em;
639 639 z-index:100;
640 640 opacity: 0.5;
641 641 }
642 642
643 643 html>body #ajax-indicator { position: fixed; }
644 644
645 645 #ajax-indicator span {
646 646 background-position: 0% 40%;
647 647 background-repeat: no-repeat;
648 648 background-image: url(../images/loading.gif);
649 649 padding-left: 26px;
650 650 vertical-align: bottom;
651 651 }
652 652
653 653 /***** Calendar *****/
654 654 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
655 655 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
656 656 table.cal thead th.week-number {width: auto;}
657 657 table.cal tbody tr {height: 100px;}
658 658 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
659 659 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
660 660 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
661 661 table.cal td.odd p.day-num {color: #bbb;}
662 662 table.cal td.today {background:#ffffdd;}
663 663 table.cal td.today p.day-num {font-weight: bold;}
664 664 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
665 665 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
666 666 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
667 667 p.cal.legend span {display:block;}
668 668
669 669 /***** Tooltips ******/
670 670 .tooltip{position:relative;z-index:24;}
671 671 .tooltip:hover{z-index:25;color:#000;}
672 672 .tooltip span.tip{display: none; text-align:left;}
673 673
674 674 div.tooltip:hover span.tip{
675 675 display:block;
676 676 position:absolute;
677 677 top:12px; left:24px; width:270px;
678 678 border:1px solid #555;
679 679 background-color:#fff;
680 680 padding: 4px;
681 681 font-size: 0.8em;
682 682 color:#505050;
683 683 }
684 684
685 685 img.ui-datepicker-trigger {
686 686 cursor: pointer;
687 687 vertical-align: middle;
688 688 margin-left: 4px;
689 689 }
690 690
691 691 /***** Progress bar *****/
692 692 table.progress {
693 693 border-collapse: collapse;
694 694 border-spacing: 0pt;
695 695 empty-cells: show;
696 696 text-align: center;
697 697 float:left;
698 698 margin: 1px 6px 1px 0px;
699 699 }
700 700
701 701 table.progress td { height: 1em; }
702 702 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
703 703 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
704 704 table.progress td.todo { background: #eee none repeat scroll 0%; }
705 705 p.percent {font-size: 80%;}
706 706 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
707 707
708 708 #roadmap table.progress td { height: 1.2em; }
709 709 /***** Tabs *****/
710 710 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
711 711 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
712 712 #content .tabs ul li {
713 713 float:left;
714 714 list-style-type:none;
715 715 white-space:nowrap;
716 716 margin-right:4px;
717 717 background:#fff;
718 718 position:relative;
719 719 margin-bottom:-1px;
720 720 }
721 721 #content .tabs ul li a{
722 722 display:block;
723 723 font-size: 0.9em;
724 724 text-decoration:none;
725 725 line-height:1.3em;
726 726 padding:4px 6px 4px 6px;
727 727 border: 1px solid #ccc;
728 728 border-bottom: 1px solid #bbbbbb;
729 729 background-color: #f6f6f6;
730 730 color:#999;
731 731 font-weight:bold;
732 732 border-top-left-radius:3px;
733 733 border-top-right-radius:3px;
734 734 }
735 735
736 736 #content .tabs ul li a:hover {
737 737 background-color: #ffffdd;
738 738 text-decoration:none;
739 739 }
740 740
741 741 #content .tabs ul li a.selected {
742 742 background-color: #fff;
743 743 border: 1px solid #bbbbbb;
744 744 border-bottom: 1px solid #fff;
745 745 color:#444;
746 746 }
747 747
748 748 #content .tabs ul li a.selected:hover {background-color: #fff;}
749 749
750 750 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
751 751
752 752 button.tab-left, button.tab-right {
753 753 font-size: 0.9em;
754 754 cursor: pointer;
755 755 height:24px;
756 756 border: 1px solid #ccc;
757 757 border-bottom: 1px solid #bbbbbb;
758 758 position:absolute;
759 759 padding:4px;
760 760 width: 20px;
761 761 bottom: -1px;
762 762 }
763 763
764 764 button.tab-left {
765 765 right: 20px;
766 766 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
767 767 border-top-left-radius:3px;
768 768 }
769 769
770 770 button.tab-right {
771 771 right: 0;
772 772 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
773 773 border-top-right-radius:3px;
774 774 }
775 775
776 776 /***** Diff *****/
777 777 .diff_out { background: #fcc; }
778 778 .diff_out span { background: #faa; }
779 779 .diff_in { background: #cfc; }
780 780 .diff_in span { background: #afa; }
781 781
782 782 .text-diff {
783 783 padding: 1em;
784 784 background-color:#f6f6f6;
785 785 color:#505050;
786 786 border: 1px solid #e4e4e4;
787 787 }
788 788
789 789 /***** Wiki *****/
790 790 div.wiki table {
791 791 border-collapse: collapse;
792 792 margin-bottom: 1em;
793 793 }
794 794
795 795 div.wiki table, div.wiki td, div.wiki th {
796 796 border: 1px solid #bbb;
797 797 padding: 4px;
798 798 }
799 799
800 800 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
801 801
802 802 div.wiki .external {
803 803 background-position: 0% 60%;
804 804 background-repeat: no-repeat;
805 805 padding-left: 12px;
806 806 background-image: url(../images/external.png);
807 807 }
808 808
809 809 div.wiki a.new {color: #b73535;}
810 810
811 811 div.wiki ul, div.wiki ol {margin-bottom:1em;}
812 812
813 813 div.wiki pre {
814 814 margin: 1em 1em 1em 1.6em;
815 815 padding: 8px;
816 816 background-color: #fafafa;
817 817 border: 1px solid #e2e2e2;
818 818 width:auto;
819 819 overflow-x: auto;
820 820 overflow-y: hidden;
821 821 }
822 822
823 823 div.wiki ul.toc {
824 824 background-color: #ffffdd;
825 825 border: 1px solid #e4e4e4;
826 826 padding: 4px;
827 827 line-height: 1.2em;
828 828 margin-bottom: 12px;
829 829 margin-right: 12px;
830 830 margin-left: 0;
831 831 display: table
832 832 }
833 833 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
834 834
835 835 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
836 836 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
837 837 div.wiki ul.toc ul { margin: 0; padding: 0; }
838 838 div.wiki ul.toc li {list-style-type:none; margin: 0; font-size:12px;}
839 839 div.wiki ul.toc li li {margin-left: 1.5em; font-size:10px;}
840 840 div.wiki ul.toc a {
841 841 font-size: 0.9em;
842 842 font-weight: normal;
843 843 text-decoration: none;
844 844 color: #606060;
845 845 }
846 846 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
847 847
848 848 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
849 849 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
850 850 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
851 851
852 852 div.wiki img { vertical-align: middle; }
853 853
854 854 /***** My page layout *****/
855 855 .block-receiver {
856 856 border:1px dashed #c0c0c0;
857 857 margin-bottom: 20px;
858 858 padding: 15px 0 15px 0;
859 859 }
860 860
861 861 .mypage-box {
862 862 margin:0 0 20px 0;
863 863 color:#505050;
864 864 line-height:1.5em;
865 865 }
866 866
867 867 .handle {cursor: move;}
868 868
869 869 a.close-icon {
870 870 display:block;
871 871 margin-top:3px;
872 872 overflow:hidden;
873 873 width:12px;
874 874 height:12px;
875 875 background-repeat: no-repeat;
876 876 cursor:pointer;
877 877 background-image:url('../images/close.png');
878 878 }
879 879 a.close-icon:hover {background-image:url('../images/close_hl.png');}
880 880
881 881 /***** Gantt chart *****/
882 882 .gantt_hdr {
883 883 position:absolute;
884 884 top:0;
885 885 height:16px;
886 886 border-top: 1px solid #c0c0c0;
887 887 border-bottom: 1px solid #c0c0c0;
888 888 border-right: 1px solid #c0c0c0;
889 889 text-align: center;
890 890 overflow: hidden;
891 891 }
892 892
893 893 .gantt_hdr.nwday {background-color:#f1f1f1;}
894 894
895 895 .gantt_subjects { font-size: 0.8em; }
896 896 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
897 897
898 898 .task {
899 899 position: absolute;
900 900 height:8px;
901 901 font-size:0.8em;
902 902 color:#888;
903 903 padding:0;
904 904 margin:0;
905 905 line-height:16px;
906 906 white-space:nowrap;
907 907 }
908 908
909 909 .task.label {width:100%;}
910 910 .task.label.project, .task.label.version { font-weight: bold; }
911 911
912 912 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
913 913 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
914 914 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
915 915
916 916 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
917 917 .task_late.parent, .task_done.parent { height: 3px;}
918 918 .task.parent.marker.starting { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;}
919 919 .task.parent.marker.ending { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;}
920 920
921 921 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
922 922 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
923 923 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
924 924 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
925 925
926 926 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
927 927 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
928 928 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
929 929 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
930 930
931 931 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
932 932 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
933 933
934 934 /***** Icons *****/
935 935 .icon {
936 936 background-position: 0% 50%;
937 937 background-repeat: no-repeat;
938 938 padding-left: 20px;
939 939 padding-top: 2px;
940 940 padding-bottom: 3px;
941 941 }
942 942
943 943 .icon-add { background-image: url(../images/add.png); }
944 944 .icon-edit { background-image: url(../images/edit.png); }
945 945 .icon-copy { background-image: url(../images/copy.png); }
946 946 .icon-duplicate { background-image: url(../images/duplicate.png); }
947 947 .icon-del { background-image: url(../images/delete.png); }
948 948 .icon-move { background-image: url(../images/move.png); }
949 949 .icon-save { background-image: url(../images/save.png); }
950 950 .icon-cancel { background-image: url(../images/cancel.png); }
951 951 .icon-multiple { background-image: url(../images/table_multiple.png); }
952 952 .icon-folder { background-image: url(../images/folder.png); }
953 953 .open .icon-folder { background-image: url(../images/folder_open.png); }
954 954 .icon-package { background-image: url(../images/package.png); }
955 955 .icon-user { background-image: url(../images/user.png); }
956 956 .icon-projects { background-image: url(../images/projects.png); }
957 957 .icon-help { background-image: url(../images/help.png); }
958 958 .icon-attachment { background-image: url(../images/attachment.png); }
959 959 .icon-history { background-image: url(../images/history.png); }
960 960 .icon-time { background-image: url(../images/time.png); }
961 961 .icon-time-add { background-image: url(../images/time_add.png); }
962 962 .icon-stats { background-image: url(../images/stats.png); }
963 963 .icon-warning { background-image: url(../images/warning.png); }
964 964 .icon-fav { background-image: url(../images/fav.png); }
965 965 .icon-fav-off { background-image: url(../images/fav_off.png); }
966 966 .icon-reload { background-image: url(../images/reload.png); }
967 967 .icon-lock { background-image: url(../images/locked.png); }
968 968 .icon-unlock { background-image: url(../images/unlock.png); }
969 969 .icon-checked { background-image: url(../images/true.png); }
970 970 .icon-details { background-image: url(../images/zoom_in.png); }
971 971 .icon-report { background-image: url(../images/report.png); }
972 972 .icon-comment { background-image: url(../images/comment.png); }
973 973 .icon-summary { background-image: url(../images/lightning.png); }
974 974 .icon-server-authentication { background-image: url(../images/server_key.png); }
975 975 .icon-issue { background-image: url(../images/ticket.png); }
976 976 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
977 977 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
978 978 .icon-passwd { background-image: url(../images/textfield_key.png); }
979 979 .icon-test { background-image: url(../images/bullet_go.png); }
980 980
981 981 .icon-file { background-image: url(../images/files/default.png); }
982 982 .icon-file.text-plain { background-image: url(../images/files/text.png); }
983 983 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
984 984 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
985 985 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
986 986 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
987 987 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
988 988 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
989 989 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
990 990 .icon-file.text-css { background-image: url(../images/files/css.png); }
991 991 .icon-file.text-html { background-image: url(../images/files/html.png); }
992 992 .icon-file.image-gif { background-image: url(../images/files/image.png); }
993 993 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
994 994 .icon-file.image-png { background-image: url(../images/files/image.png); }
995 995 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
996 996 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
997 997 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
998 998 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
999 999
1000 1000 img.gravatar {
1001 1001 padding: 2px;
1002 1002 border: solid 1px #d5d5d5;
1003 1003 background: #fff;
1004 1004 vertical-align: middle;
1005 1005 }
1006 1006
1007 1007 div.issue img.gravatar {
1008 1008 float: left;
1009 1009 margin: 0 6px 0 0;
1010 1010 padding: 5px;
1011 1011 }
1012 1012
1013 1013 div.issue table img.gravatar {
1014 1014 height: 14px;
1015 1015 width: 14px;
1016 1016 padding: 2px;
1017 1017 float: left;
1018 1018 margin: 0 0.5em 0 0;
1019 1019 }
1020 1020
1021 1021 h2 img.gravatar {margin: -2px 4px -4px 0;}
1022 1022 h3 img.gravatar {margin: -4px 4px -4px 0;}
1023 1023 h4 img.gravatar {margin: -6px 4px -4px 0;}
1024 1024 td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
1025 1025 #activity dt img.gravatar {float: left; margin: 0 1em 1em 0;}
1026 1026 /* Used on 12px Gravatar img tags without the icon background */
1027 1027 .icon-gravatar {float: left; margin-right: 4px;}
1028 1028
1029 1029 #activity dt, .journal {clear: left;}
1030 1030
1031 1031 .journal-link {float: right;}
1032 1032
1033 1033 h2 img { vertical-align:middle; }
1034 1034
1035 1035 .hascontextmenu { cursor: context-menu; }
1036 1036
1037 1037 /************* CodeRay styles *************/
1038 1038 .syntaxhl div {display: inline;}
1039 1039 .syntaxhl .line-numbers {padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px;}
1040 1040 .syntaxhl .code pre { overflow: auto }
1041 1041 .syntaxhl .debug { color: white !important; background: blue !important; }
1042 1042
1043 1043 .syntaxhl .annotation { color:#007 }
1044 1044 .syntaxhl .attribute-name { color:#b48 }
1045 1045 .syntaxhl .attribute-value { color:#700 }
1046 1046 .syntaxhl .binary { color:#509 }
1047 1047 .syntaxhl .char .content { color:#D20 }
1048 1048 .syntaxhl .char .delimiter { color:#710 }
1049 1049 .syntaxhl .char { color:#D20 }
1050 1050 .syntaxhl .class { color:#258; font-weight:bold }
1051 1051 .syntaxhl .class-variable { color:#369 }
1052 1052 .syntaxhl .color { color:#0A0 }
1053 1053 .syntaxhl .comment { color:#385 }
1054 1054 .syntaxhl .comment .char { color:#385 }
1055 1055 .syntaxhl .comment .delimiter { color:#385 }
1056 1056 .syntaxhl .complex { color:#A08 }
1057 1057 .syntaxhl .constant { color:#258; font-weight:bold }
1058 1058 .syntaxhl .decorator { color:#B0B }
1059 1059 .syntaxhl .definition { color:#099; font-weight:bold }
1060 1060 .syntaxhl .delimiter { color:black }
1061 1061 .syntaxhl .directive { color:#088; font-weight:bold }
1062 1062 .syntaxhl .doc { color:#970 }
1063 1063 .syntaxhl .doc-string { color:#D42; font-weight:bold }
1064 1064 .syntaxhl .doctype { color:#34b }
1065 1065 .syntaxhl .entity { color:#800; font-weight:bold }
1066 1066 .syntaxhl .error { color:#F00; background-color:#FAA }
1067 1067 .syntaxhl .escape { color:#666 }
1068 1068 .syntaxhl .exception { color:#C00; font-weight:bold }
1069 1069 .syntaxhl .float { color:#06D }
1070 1070 .syntaxhl .function { color:#06B; font-weight:bold }
1071 1071 .syntaxhl .global-variable { color:#d70 }
1072 1072 .syntaxhl .hex { color:#02b }
1073 1073 .syntaxhl .imaginary { color:#f00 }
1074 1074 .syntaxhl .include { color:#B44; font-weight:bold }
1075 1075 .syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
1076 1076 .syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
1077 1077 .syntaxhl .instance-variable { color:#33B }
1078 1078 .syntaxhl .integer { color:#06D }
1079 1079 .syntaxhl .key .char { color: #60f }
1080 1080 .syntaxhl .key .delimiter { color: #404 }
1081 1081 .syntaxhl .key { color: #606 }
1082 1082 .syntaxhl .keyword { color:#939; font-weight:bold }
1083 1083 .syntaxhl .label { color:#970; font-weight:bold }
1084 1084 .syntaxhl .local-variable { color:#963 }
1085 1085 .syntaxhl .namespace { color:#707; font-weight:bold }
1086 1086 .syntaxhl .octal { color:#40E }
1087 1087 .syntaxhl .operator { }
1088 1088 .syntaxhl .predefined { color:#369; font-weight:bold }
1089 1089 .syntaxhl .predefined-constant { color:#069 }
1090 1090 .syntaxhl .predefined-type { color:#0a5; font-weight:bold }
1091 1091 .syntaxhl .preprocessor { color:#579 }
1092 1092 .syntaxhl .pseudo-class { color:#00C; font-weight:bold }
1093 1093 .syntaxhl .regexp .content { color:#808 }
1094 1094 .syntaxhl .regexp .delimiter { color:#404 }
1095 1095 .syntaxhl .regexp .modifier { color:#C2C }
1096 1096 .syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
1097 1097 .syntaxhl .reserved { color:#080; font-weight:bold }
1098 1098 .syntaxhl .shell .content { color:#2B2 }
1099 1099 .syntaxhl .shell .delimiter { color:#161 }
1100 1100 .syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
1101 1101 .syntaxhl .string .char { color: #46a }
1102 1102 .syntaxhl .string .content { color: #46a }
1103 1103 .syntaxhl .string .delimiter { color: #46a }
1104 1104 .syntaxhl .string .modifier { color: #46a }
1105 1105 .syntaxhl .symbol .content { color:#d33 }
1106 1106 .syntaxhl .symbol .delimiter { color:#d33 }
1107 1107 .syntaxhl .symbol { color:#d33 }
1108 1108 .syntaxhl .tag { color:#070 }
1109 1109 .syntaxhl .type { color:#339; font-weight:bold }
1110 1110 .syntaxhl .value { color: #088; }
1111 1111 .syntaxhl .variable { color:#037 }
1112 1112
1113 1113 .syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
1114 1114 .syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
1115 1115 .syntaxhl .change { color: #bbf; background: #007; }
1116 1116 .syntaxhl .head { color: #f8f; background: #505 }
1117 1117 .syntaxhl .head .filename { color: white; }
1118 1118
1119 1119 .syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; }
1120 1120 .syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }
1121 1121
1122 1122 .syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
1123 1123 .syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
1124 1124 .syntaxhl .change .change { color: #88f }
1125 1125 .syntaxhl .head .head { color: #f4f }
1126 1126
1127 1127 /***** Media print specific styles *****/
1128 1128 @media print {
1129 1129 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1130 1130 #main { background: #fff; }
1131 1131 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1132 1132 #wiki_add_attachment { display:none; }
1133 1133 .hide-when-print { display: none; }
1134 1134 .autoscroll {overflow-x: visible;}
1135 1135 table.list {margin-top:0.5em;}
1136 1136 table.list th, table.list td {border: 1px solid #aaa;}
1137 1137 }
1138 1138
1139 1139 /* Accessibility specific styles */
1140 1140 .hidden-for-sighted {
1141 1141 position:absolute;
1142 1142 left:-10000px;
1143 1143 top:auto;
1144 1144 width:1px;
1145 1145 height:1px;
1146 1146 overflow:hidden;
1147 1147 }
@@ -1,205 +1,202
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class GroupsControllerTest < ActionController::TestCase
21 21 fixtures :projects, :users, :members, :member_roles, :roles, :groups_users
22 22
23 23 def setup
24 24 @request.session[:user_id] = 1
25 25 end
26 26
27 27 def test_index
28 28 get :index
29 29 assert_response :success
30 30 assert_template 'index'
31 31 end
32 32
33 33 def test_show
34 34 get :show, :id => 10
35 35 assert_response :success
36 36 assert_template 'show'
37 37 end
38 38
39 39 def test_show_invalid_should_return_404
40 40 get :show, :id => 99
41 41 assert_response 404
42 42 end
43 43
44 44 def test_new
45 45 get :new
46 46 assert_response :success
47 47 assert_template 'new'
48 48 assert_select 'input[name=?]', 'group[name]'
49 49 end
50 50
51 51 def test_create
52 52 assert_difference 'Group.count' do
53 53 post :create, :group => {:name => 'New group'}
54 54 end
55 55 assert_redirected_to '/groups'
56 56 group = Group.first(:order => 'id DESC')
57 57 assert_equal 'New group', group.name
58 58 assert_equal [], group.users
59 59 end
60 60
61 61 def test_create_and_continue
62 62 assert_difference 'Group.count' do
63 63 post :create, :group => {:name => 'New group'}, :continue => 'Create and continue'
64 64 end
65 65 assert_redirected_to '/groups/new'
66 66 group = Group.first(:order => 'id DESC')
67 67 assert_equal 'New group', group.name
68 68 end
69 69
70 70 def test_create_with_failure
71 71 assert_no_difference 'Group.count' do
72 72 post :create, :group => {:name => ''}
73 73 end
74 74 assert_response :success
75 75 assert_template 'new'
76 76 end
77 77
78 78 def test_edit
79 79 get :edit, :id => 10
80 80 assert_response :success
81 81 assert_template 'edit'
82 82
83 83 assert_select 'div#tab-content-users'
84 84 assert_select 'div#tab-content-memberships' do
85 85 assert_select 'a', :text => 'Private child of eCookbook'
86 86 end
87 87 end
88 88
89 89 def test_update
90 90 new_name = 'New name'
91 91 put :update, :id => 10, :group => {:name => new_name}
92 92 assert_redirected_to '/groups'
93 93 group = Group.find(10)
94 94 assert_equal new_name, group.name
95 95 end
96 96
97 97 def test_update_with_failure
98 98 put :update, :id => 10, :group => {:name => ''}
99 99 assert_response :success
100 100 assert_template 'edit'
101 101 end
102 102
103 103 def test_destroy
104 104 assert_difference 'Group.count', -1 do
105 105 post :destroy, :id => 10
106 106 end
107 107 assert_redirected_to '/groups'
108 108 end
109 109
110 110 def test_add_users
111 111 assert_difference 'Group.find(10).users.count', 2 do
112 112 post :add_users, :id => 10, :user_ids => ['2', '3']
113 113 end
114 114 end
115 115
116 116 def test_xhr_add_users
117 117 assert_difference 'Group.find(10).users.count', 2 do
118 118 xhr :post, :add_users, :id => 10, :user_ids => ['2', '3']
119 119 assert_response :success
120 120 assert_template 'add_users'
121 121 assert_equal 'text/javascript', response.content_type
122 122 end
123 123 assert_match /John Smith/, response.body
124 124 end
125 125
126 126 def test_remove_user
127 127 assert_difference 'Group.find(10).users.count', -1 do
128 128 delete :remove_user, :id => 10, :user_id => '8'
129 129 end
130 130 end
131 131
132 132 def test_xhr_remove_user
133 133 assert_difference 'Group.find(10).users.count', -1 do
134 134 xhr :delete, :remove_user, :id => 10, :user_id => '8'
135 135 assert_response :success
136 136 assert_template 'remove_user'
137 137 assert_equal 'text/javascript', response.content_type
138 138 end
139 139 end
140 140
141 141 def test_new_membership
142 142 assert_difference 'Group.find(10).members.count' do
143 143 post :edit_membership, :id => 10, :membership => { :project_id => 2, :role_ids => ['1', '2']}
144 144 end
145 145 end
146 146
147 147 def test_xhr_new_membership
148 148 assert_difference 'Group.find(10).members.count' do
149 149 xhr :post, :edit_membership, :id => 10, :membership => { :project_id => 2, :role_ids => ['1', '2']}
150 150 assert_response :success
151 151 assert_template 'edit_membership'
152 152 assert_equal 'text/javascript', response.content_type
153 153 end
154 154 assert_match /OnlineStore/, response.body
155 155 end
156 156
157 157 def test_xhr_new_membership_with_failure
158 158 assert_no_difference 'Group.find(10).members.count' do
159 159 xhr :post, :edit_membership, :id => 10, :membership => { :project_id => 999, :role_ids => ['1', '2']}
160 160 assert_response :success
161 161 assert_template 'edit_membership'
162 162 assert_equal 'text/javascript', response.content_type
163 163 end
164 164 assert_match /alert/, response.body, "Alert message not sent"
165 165 end
166 166
167 167 def test_edit_membership
168 168 assert_no_difference 'Group.find(10).members.count' do
169 169 post :edit_membership, :id => 10, :membership_id => 6, :membership => { :role_ids => ['1', '3']}
170 170 end
171 171 end
172 172
173 173 def test_xhr_edit_membership
174 174 assert_no_difference 'Group.find(10).members.count' do
175 175 xhr :post, :edit_membership, :id => 10, :membership_id => 6, :membership => { :role_ids => ['1', '3']}
176 176 assert_response :success
177 177 assert_template 'edit_membership'
178 178 assert_equal 'text/javascript', response.content_type
179 179 end
180 180 end
181 181
182 182 def test_destroy_membership
183 183 assert_difference 'Group.find(10).members.count', -1 do
184 184 post :destroy_membership, :id => 10, :membership_id => 6
185 185 end
186 186 end
187 187
188 188 def test_xhr_destroy_membership
189 189 assert_difference 'Group.find(10).members.count', -1 do
190 190 xhr :post, :destroy_membership, :id => 10, :membership_id => 6
191 191 assert_response :success
192 192 assert_template 'destroy_membership'
193 193 assert_equal 'text/javascript', response.content_type
194 194 end
195 195 end
196 196
197 197 def test_autocomplete_for_user
198 get :autocomplete_for_user, :id => 10, :q => 'mis'
198 get :autocomplete_for_user, :id => 10, :q => 'smi', :format => 'js'
199 199 assert_response :success
200 users = assigns(:users)
201 assert_not_nil users
202 assert users.any?
203 assert !users.include?(Group.find(10).users.first)
200 assert_include 'John Smith', response.body
204 201 end
205 202 end
@@ -1,114 +1,111
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class MembersControllerTest < ActionController::TestCase
21 21 fixtures :projects, :members, :member_roles, :roles, :users
22 22
23 23 def setup
24 24 User.current = nil
25 25 @request.session[:user_id] = 2
26 26 end
27 27
28 28 def test_create
29 29 assert_difference 'Member.count' do
30 30 post :create, :project_id => 1, :membership => {:role_ids => [1], :user_id => 7}
31 31 end
32 32 assert_redirected_to '/projects/ecookbook/settings/members'
33 33 assert User.find(7).member_of?(Project.find(1))
34 34 end
35 35
36 36 def test_create_multiple
37 37 assert_difference 'Member.count', 3 do
38 38 post :create, :project_id => 1, :membership => {:role_ids => [1], :user_ids => [7, 8, 9]}
39 39 end
40 40 assert_redirected_to '/projects/ecookbook/settings/members'
41 41 assert User.find(7).member_of?(Project.find(1))
42 42 end
43 43
44 44 def test_xhr_create
45 45 assert_difference 'Member.count', 3 do
46 46 xhr :post, :create, :project_id => 1, :membership => {:role_ids => [1], :user_ids => [7, 8, 9]}
47 47 assert_response :success
48 48 assert_template 'create'
49 49 assert_equal 'text/javascript', response.content_type
50 50 end
51 51 assert User.find(7).member_of?(Project.find(1))
52 52 assert User.find(8).member_of?(Project.find(1))
53 53 assert User.find(9).member_of?(Project.find(1))
54 54 assert_include 'tab-content-members', response.body
55 55 end
56 56
57 57 def test_xhr_create_with_failure
58 58 assert_no_difference 'Member.count' do
59 59 xhr :post, :create, :project_id => 1, :membership => {:role_ids => [], :user_ids => [7, 8, 9]}
60 60 assert_response :success
61 61 assert_template 'create'
62 62 assert_equal 'text/javascript', response.content_type
63 63 end
64 64 assert_match /alert/, response.body, "Alert message not sent"
65 65 end
66 66
67 67 def test_edit
68 68 assert_no_difference 'Member.count' do
69 69 put :update, :id => 2, :membership => {:role_ids => [1], :user_id => 3}
70 70 end
71 71 assert_redirected_to '/projects/ecookbook/settings/members'
72 72 end
73 73
74 74 def test_xhr_edit
75 75 assert_no_difference 'Member.count' do
76 76 xhr :put, :update, :id => 2, :membership => {:role_ids => [1], :user_id => 3}
77 77 assert_response :success
78 78 assert_template 'update'
79 79 assert_equal 'text/javascript', response.content_type
80 80 end
81 81 member = Member.find(2)
82 82 assert_equal [1], member.role_ids
83 83 assert_equal 3, member.user_id
84 84 assert_include 'tab-content-members', response.body
85 85 end
86 86
87 87 def test_destroy
88 88 assert_difference 'Member.count', -1 do
89 89 delete :destroy, :id => 2
90 90 end
91 91 assert_redirected_to '/projects/ecookbook/settings/members'
92 92 assert !User.find(3).member_of?(Project.find(1))
93 93 end
94 94
95 95 def test_xhr_destroy
96 96 assert_difference 'Member.count', -1 do
97 97 xhr :delete, :destroy, :id => 2
98 98 assert_response :success
99 99 assert_template 'destroy'
100 100 assert_equal 'text/javascript', response.content_type
101 101 end
102 102 assert_nil Member.find_by_id(2)
103 103 assert_include 'tab-content-members', response.body
104 104 end
105 105
106 106 def test_autocomplete
107 get :autocomplete, :project_id => 1, :q => 'mis'
107 get :autocomplete, :project_id => 1, :q => 'mis', :format => 'js'
108 108 assert_response :success
109 assert_template 'autocomplete'
110
111 assert_tag :label, :content => /User Misc/,
112 :child => { :tag => 'input', :attributes => { :name => 'membership[user_ids][]', :value => '8' } }
109 assert_include 'User Misc', response.body
113 110 end
114 111 end
@@ -1,102 +1,106
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../test_helper', __FILE__)
19 19
20 20 class RoutingGroupsTest < ActionController::IntegrationTest
21 21 def test_groups_resources
22 22 assert_routing(
23 23 { :method => 'get', :path => "/groups" },
24 24 { :controller => 'groups', :action => 'index' }
25 25 )
26 26 assert_routing(
27 27 { :method => 'get', :path => "/groups.xml" },
28 28 { :controller => 'groups', :action => 'index', :format => 'xml' }
29 29 )
30 30 assert_routing(
31 31 { :method => 'post', :path => "/groups" },
32 32 { :controller => 'groups', :action => 'create' }
33 33 )
34 34 assert_routing(
35 35 { :method => 'post', :path => "/groups.xml" },
36 36 { :controller => 'groups', :action => 'create', :format => 'xml' }
37 37 )
38 38 assert_routing(
39 39 { :method => 'get', :path => "/groups/new" },
40 40 { :controller => 'groups', :action => 'new' }
41 41 )
42 42 assert_routing(
43 43 { :method => 'get', :path => "/groups/1/edit" },
44 44 { :controller => 'groups', :action => 'edit', :id => '1' }
45 45 )
46 46 assert_routing(
47 47 { :method => 'get', :path => "/groups/1/autocomplete_for_user" },
48 48 { :controller => 'groups', :action => 'autocomplete_for_user', :id => '1' }
49 49 )
50 50 assert_routing(
51 { :method => 'get', :path => "/groups/1/autocomplete_for_user.js" },
52 { :controller => 'groups', :action => 'autocomplete_for_user', :id => '1', :format => 'js' }
53 )
54 assert_routing(
51 55 { :method => 'get', :path => "/groups/1" },
52 56 { :controller => 'groups', :action => 'show', :id => '1' }
53 57 )
54 58 assert_routing(
55 59 { :method => 'get', :path => "/groups/1.xml" },
56 60 { :controller => 'groups', :action => 'show', :id => '1', :format => 'xml' }
57 61 )
58 62 assert_routing(
59 63 { :method => 'put', :path => "/groups/1" },
60 64 { :controller => 'groups', :action => 'update', :id => '1' }
61 65 )
62 66 assert_routing(
63 67 { :method => 'put', :path => "/groups/1.xml" },
64 68 { :controller => 'groups', :action => 'update', :id => '1', :format => 'xml' }
65 69 )
66 70 assert_routing(
67 71 { :method => 'delete', :path => "/groups/1" },
68 72 { :controller => 'groups', :action => 'destroy', :id => '1' }
69 73 )
70 74 assert_routing(
71 75 { :method => 'delete', :path => "/groups/1.xml" },
72 76 { :controller => 'groups', :action => 'destroy', :id => '1', :format => 'xml' }
73 77 )
74 78 end
75 79
76 80 def test_groups
77 81 assert_routing(
78 82 { :method => 'post', :path => "/groups/567/users" },
79 83 { :controller => 'groups', :action => 'add_users', :id => '567' }
80 84 )
81 85 assert_routing(
82 86 { :method => 'post', :path => "/groups/567/users.xml" },
83 87 { :controller => 'groups', :action => 'add_users', :id => '567', :format => 'xml' }
84 88 )
85 89 assert_routing(
86 90 { :method => 'delete', :path => "/groups/567/users/12" },
87 91 { :controller => 'groups', :action => 'remove_user', :id => '567', :user_id => '12' }
88 92 )
89 93 assert_routing(
90 94 { :method => 'delete', :path => "/groups/567/users/12.xml" },
91 95 { :controller => 'groups', :action => 'remove_user', :id => '567', :user_id => '12', :format => 'xml' }
92 96 )
93 97 assert_routing(
94 98 { :method => 'post', :path => "/groups/destroy_membership/567" },
95 99 { :controller => 'groups', :action => 'destroy_membership', :id => '567' }
96 100 )
97 101 assert_routing(
98 102 { :method => 'post', :path => "/groups/edit_membership/567" },
99 103 { :controller => 'groups', :action => 'edit_membership', :id => '567' }
100 104 )
101 105 end
102 106 end
@@ -1,59 +1,63
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../test_helper', __FILE__)
19 19
20 20 class RoutingMembersTest < ActionController::IntegrationTest
21 21 def test_members
22 22 assert_routing(
23 23 { :method => 'get', :path => "/projects/5234/memberships.xml" },
24 24 { :controller => 'members', :action => 'index', :project_id => '5234', :format => 'xml' }
25 25 )
26 26 assert_routing(
27 27 { :method => 'get', :path => "/memberships/5234.xml" },
28 28 { :controller => 'members', :action => 'show', :id => '5234', :format => 'xml' }
29 29 )
30 30 assert_routing(
31 31 { :method => 'post', :path => "/projects/5234/memberships" },
32 32 { :controller => 'members', :action => 'create', :project_id => '5234' }
33 33 )
34 34 assert_routing(
35 35 { :method => 'post', :path => "/projects/5234/memberships.xml" },
36 36 { :controller => 'members', :action => 'create', :project_id => '5234', :format => 'xml' }
37 37 )
38 38 assert_routing(
39 39 { :method => 'put', :path => "/memberships/5234" },
40 40 { :controller => 'members', :action => 'update', :id => '5234' }
41 41 )
42 42 assert_routing(
43 43 { :method => 'put', :path => "/memberships/5234.xml" },
44 44 { :controller => 'members', :action => 'update', :id => '5234', :format => 'xml' }
45 45 )
46 46 assert_routing(
47 47 { :method => 'delete', :path => "/memberships/5234" },
48 48 { :controller => 'members', :action => 'destroy', :id => '5234' }
49 49 )
50 50 assert_routing(
51 51 { :method => 'delete', :path => "/memberships/5234.xml" },
52 52 { :controller => 'members', :action => 'destroy', :id => '5234', :format => 'xml' }
53 53 )
54 54 assert_routing(
55 55 { :method => 'get', :path => "/projects/5234/memberships/autocomplete" },
56 56 { :controller => 'members', :action => 'autocomplete', :project_id => '5234' }
57 57 )
58 assert_routing(
59 { :method => 'get', :path => "/projects/5234/memberships/autocomplete.js" },
60 { :controller => 'members', :action => 'autocomplete', :project_id => '5234', :format => 'js' }
61 )
58 62 end
59 63 end
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now