##// END OF EJS Templates
#538 form for permissions can handle multiple users at once
marcink -
r2759:c61c2cce beta
parent child Browse files
Show More
@@ -1,711 +1,714 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7 1.4.0 (**2012-XX-XX**)
7 1.4.0 (**2012-XX-XX**)
8 ----------------------
8 ----------------------
9
9
10 :status: in-progress
10 :status: in-progress
11 :branch: beta
11 :branch: beta
12
12
13 news
13 news
14 ++++
14 ++++
15
15
16 - new codereview system
16 - new codereview system
17 - email map, allowing users to have multiple email addresses mapped into
17 - email map, allowing users to have multiple email addresses mapped into
18 their accounts
18 their accounts
19 - improved git-hook system. Now all actions for git are logged into journal
19 - improved git-hook system. Now all actions for git are logged into journal
20 including pushed revisions, user and IP address
20 including pushed revisions, user and IP address
21 - changed setup-app into setup-rhodecode and added default options to it.
21 - changed setup-app into setup-rhodecode and added default options to it.
22 - new git repos are created as bare now by default
22 - new git repos are created as bare now by default
23 - #464 added links to groups in permission box
23 - #464 added links to groups in permission box
24 - #465 mentions autocomplete inside comments boxes
24 - #465 mentions autocomplete inside comments boxes
25 - #469 added --update-only option to whoosh to re-index only given list
25 - #469 added --update-only option to whoosh to re-index only given list
26 of repos in index
26 of repos in index
27 - rhodecode-api CLI client
27 - rhodecode-api CLI client
28 - new git http protocol replaced buggy dulwich implementation.
28 - new git http protocol replaced buggy dulwich implementation.
29 Now based on pygrack & gitweb
29 Now based on pygrack & gitweb
30 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
30 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
31 reformated based on user suggestions. Additional rss/atom feeds for user
31 reformated based on user suggestions. Additional rss/atom feeds for user
32 journal
32 journal
33 - various i18n improvements
33 - various i18n improvements
34 - #478 permissions overview for admin in user edit view
34 - #478 permissions overview for admin in user edit view
35 - File view now displays small gravatars off all authors of given file
35 - File view now displays small gravatars off all authors of given file
36 - Implemented landing revisions. Each repository will get landing_rev attribute
36 - Implemented landing revisions. Each repository will get landing_rev attribute
37 that defines 'default' revision/branch for generating readme files
37 that defines 'default' revision/branch for generating readme files
38 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested.
38 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested.
39 - Import remote svn repositories to mercurial using hgsubversion
39 - Import remote svn repositories to mercurial using hgsubversion
40 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
40 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
41 - RhodeCode can use alternative server for generating avatar icons
41 - RhodeCode can use alternative server for generating avatar icons
42 - implemented repositories locking. Pull locks, push unlocks. Also can be done
43 via API calls
44 - #538 form for permissions can handle multiple users at once
42
45
43 fixes
46 fixes
44 +++++
47 +++++
45
48
46 - improved translations
49 - improved translations
47 - fixes issue #455 Creating an archive generates an exception on Windows
50 - fixes issue #455 Creating an archive generates an exception on Windows
48 - fixes #448 Download ZIP archive keeps file in /tmp open and results
51 - fixes #448 Download ZIP archive keeps file in /tmp open and results
49 in out of disk space
52 in out of disk space
50 - fixes issue #454 Search results under Windows include proceeding
53 - fixes issue #454 Search results under Windows include proceeding
51 backslash
54 backslash
52 - fixed issue #450. Rhodecode no longer will crash when bad revision is
55 - fixed issue #450. Rhodecode no longer will crash when bad revision is
53 present in journal data.
56 present in journal data.
54 - fix for issue #417, git execution was broken on windows for certain
57 - fix for issue #417, git execution was broken on windows for certain
55 commands.
58 commands.
56 - fixed #413. Don't disable .git directory for bare repos on deleting
59 - fixed #413. Don't disable .git directory for bare repos on deleting
57 - fixed issue #459. Changed the way of obtaining logger in reindex task.
60 - fixed issue #459. Changed the way of obtaining logger in reindex task.
58 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
61 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
59 reindexing modified files
62 reindexing modified files
60 - fixed #481 rhodecode emails are sent without Date header
63 - fixed #481 rhodecode emails are sent without Date header
61 - fixed #458 wrong count when no repos are present
64 - fixed #458 wrong count when no repos are present
62 - fixed issue #492 missing `\ No newline at end of file` test at the end of
65 - fixed issue #492 missing `\ No newline at end of file` test at the end of
63 new chunk in html diff
66 new chunk in html diff
64 - full text search now works also for commit messages
67 - full text search now works also for commit messages
65
68
66 1.3.6 (**2012-05-17**)
69 1.3.6 (**2012-05-17**)
67 ----------------------
70 ----------------------
68
71
69 news
72 news
70 ++++
73 ++++
71
74
72 - chinese traditional translation
75 - chinese traditional translation
73 - changed setup-app into setup-rhodecode and added arguments for auto-setup
76 - changed setup-app into setup-rhodecode and added arguments for auto-setup
74 mode that doesn't need user interaction
77 mode that doesn't need user interaction
75
78
76 fixes
79 fixes
77 +++++
80 +++++
78
81
79 - fixed no scm found warning
82 - fixed no scm found warning
80 - fixed __future__ import error on rcextensions
83 - fixed __future__ import error on rcextensions
81 - made simplejson required lib for speedup on JSON encoding
84 - made simplejson required lib for speedup on JSON encoding
82 - fixes #449 bad regex could get more than revisions from parsing history
85 - fixes #449 bad regex could get more than revisions from parsing history
83 - don't clear DB session when CELERY_EAGER is turned ON
86 - don't clear DB session when CELERY_EAGER is turned ON
84
87
85 1.3.5 (**2012-05-10**)
88 1.3.5 (**2012-05-10**)
86 ----------------------
89 ----------------------
87
90
88 news
91 news
89 ++++
92 ++++
90
93
91 - use ext_json for json module
94 - use ext_json for json module
92 - unified annotation view with file source view
95 - unified annotation view with file source view
93 - notification improvements, better inbox + css
96 - notification improvements, better inbox + css
94 - #419 don't strip passwords for login forms, make rhodecode
97 - #419 don't strip passwords for login forms, make rhodecode
95 more compatible with LDAP servers
98 more compatible with LDAP servers
96 - Added HTTP_X_FORWARDED_FOR as another method of extracting
99 - Added HTTP_X_FORWARDED_FOR as another method of extracting
97 IP for pull/push logs. - moved all to base controller
100 IP for pull/push logs. - moved all to base controller
98 - #415: Adding comment to changeset causes reload.
101 - #415: Adding comment to changeset causes reload.
99 Comments are now added via ajax and doesn't reload the page
102 Comments are now added via ajax and doesn't reload the page
100 - #374 LDAP config is discarded when LDAP can't be activated
103 - #374 LDAP config is discarded when LDAP can't be activated
101 - limited push/pull operations are now logged for git in the journal
104 - limited push/pull operations are now logged for git in the journal
102 - bumped mercurial to 2.2.X series
105 - bumped mercurial to 2.2.X series
103 - added support for displaying submodules in file-browser
106 - added support for displaying submodules in file-browser
104 - #421 added bookmarks in changelog view
107 - #421 added bookmarks in changelog view
105
108
106 fixes
109 fixes
107 +++++
110 +++++
108
111
109 - fixed dev-version marker for stable when served from source codes
112 - fixed dev-version marker for stable when served from source codes
110 - fixed missing permission checks on show forks page
113 - fixed missing permission checks on show forks page
111 - #418 cast to unicode fixes in notification objects
114 - #418 cast to unicode fixes in notification objects
112 - #426 fixed mention extracting regex
115 - #426 fixed mention extracting regex
113 - fixed remote-pulling for git remotes remopositories
116 - fixed remote-pulling for git remotes remopositories
114 - fixed #434: Error when accessing files or changesets of a git repository
117 - fixed #434: Error when accessing files or changesets of a git repository
115 with submodules
118 with submodules
116 - fixed issue with empty APIKEYS for users after registration ref. #438
119 - fixed issue with empty APIKEYS for users after registration ref. #438
117 - fixed issue with getting README files from git repositories
120 - fixed issue with getting README files from git repositories
118
121
119 1.3.4 (**2012-03-28**)
122 1.3.4 (**2012-03-28**)
120 ----------------------
123 ----------------------
121
124
122 news
125 news
123 ++++
126 ++++
124
127
125 - Whoosh logging is now controlled by the .ini files logging setup
128 - Whoosh logging is now controlled by the .ini files logging setup
126 - added clone-url into edit form on /settings page
129 - added clone-url into edit form on /settings page
127 - added help text into repo add/edit forms
130 - added help text into repo add/edit forms
128 - created rcextensions module with additional mappings (ref #322) and
131 - created rcextensions module with additional mappings (ref #322) and
129 post push/pull/create repo hooks callbacks
132 post push/pull/create repo hooks callbacks
130 - implemented #377 Users view for his own permissions on account page
133 - implemented #377 Users view for his own permissions on account page
131 - #399 added inheritance of permissions for users group on repos groups
134 - #399 added inheritance of permissions for users group on repos groups
132 - #401 repository group is automatically pre-selected when adding repos
135 - #401 repository group is automatically pre-selected when adding repos
133 inside a repository group
136 inside a repository group
134 - added alternative HTTP 403 response when client failed to authenticate. Helps
137 - added alternative HTTP 403 response when client failed to authenticate. Helps
135 solving issues with Mercurial and LDAP
138 solving issues with Mercurial and LDAP
136 - #402 removed group prefix from repository name when listing repositories
139 - #402 removed group prefix from repository name when listing repositories
137 inside a group
140 inside a group
138 - added gravatars into permission view and permissions autocomplete
141 - added gravatars into permission view and permissions autocomplete
139 - #347 when running multiple RhodeCode instances, properly invalidates cache
142 - #347 when running multiple RhodeCode instances, properly invalidates cache
140 for all registered servers
143 for all registered servers
141
144
142 fixes
145 fixes
143 +++++
146 +++++
144
147
145 - fixed #390 cache invalidation problems on repos inside group
148 - fixed #390 cache invalidation problems on repos inside group
146 - fixed #385 clone by ID url was loosing proxy prefix in URL
149 - fixed #385 clone by ID url was loosing proxy prefix in URL
147 - fixed some unicode problems with waitress
150 - fixed some unicode problems with waitress
148 - fixed issue with escaping < and > in changeset commits
151 - fixed issue with escaping < and > in changeset commits
149 - fixed error occurring during recursive group creation in API
152 - fixed error occurring during recursive group creation in API
150 create_repo function
153 create_repo function
151 - fixed #393 py2.5 fixes for routes url generator
154 - fixed #393 py2.5 fixes for routes url generator
152 - fixed #397 Private repository groups shows up before login
155 - fixed #397 Private repository groups shows up before login
153 - fixed #396 fixed problems with revoking users in nested groups
156 - fixed #396 fixed problems with revoking users in nested groups
154 - fixed mysql unicode issues + specified InnoDB as default engine with
157 - fixed mysql unicode issues + specified InnoDB as default engine with
155 utf8 charset
158 utf8 charset
156 - #406 trim long branch/tag names in changelog to not break UI
159 - #406 trim long branch/tag names in changelog to not break UI
157
160
158 1.3.3 (**2012-03-02**)
161 1.3.3 (**2012-03-02**)
159 ----------------------
162 ----------------------
160
163
161 news
164 news
162 ++++
165 ++++
163
166
164
167
165 fixes
168 fixes
166 +++++
169 +++++
167
170
168 - fixed some python2.5 compatibility issues
171 - fixed some python2.5 compatibility issues
169 - fixed issues with removed repos was accidentally added as groups, after
172 - fixed issues with removed repos was accidentally added as groups, after
170 full rescan of paths
173 full rescan of paths
171 - fixes #376 Cannot edit user (using container auth)
174 - fixes #376 Cannot edit user (using container auth)
172 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
175 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
173 configuration
176 configuration
174 - fixed initial sorting of repos inside repo group
177 - fixed initial sorting of repos inside repo group
175 - fixes issue when user tried to resubmit same permission into user/user_groups
178 - fixes issue when user tried to resubmit same permission into user/user_groups
176 - bumped beaker version that fixes #375 leap error bug
179 - bumped beaker version that fixes #375 leap error bug
177 - fixed raw_changeset for git. It was generated with hg patch headers
180 - fixed raw_changeset for git. It was generated with hg patch headers
178 - fixed vcs issue with last_changeset for filenodes
181 - fixed vcs issue with last_changeset for filenodes
179 - fixed missing commit after hook delete
182 - fixed missing commit after hook delete
180 - fixed #372 issues with git operation detection that caused a security issue
183 - fixed #372 issues with git operation detection that caused a security issue
181 for git repos
184 for git repos
182
185
183 1.3.2 (**2012-02-28**)
186 1.3.2 (**2012-02-28**)
184 ----------------------
187 ----------------------
185
188
186 news
189 news
187 ++++
190 ++++
188
191
189
192
190 fixes
193 fixes
191 +++++
194 +++++
192
195
193 - fixed git protocol issues with repos-groups
196 - fixed git protocol issues with repos-groups
194 - fixed git remote repos validator that prevented from cloning remote git repos
197 - fixed git remote repos validator that prevented from cloning remote git repos
195 - fixes #370 ending slashes fixes for repo and groups
198 - fixes #370 ending slashes fixes for repo and groups
196 - fixes #368 improved git-protocol detection to handle other clients
199 - fixes #368 improved git-protocol detection to handle other clients
197 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
200 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
198 Moved To Root
201 Moved To Root
199 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
202 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
200 - fixed #373 missing cascade drop on user_group_to_perm table
203 - fixed #373 missing cascade drop on user_group_to_perm table
201
204
202 1.3.1 (**2012-02-27**)
205 1.3.1 (**2012-02-27**)
203 ----------------------
206 ----------------------
204
207
205 news
208 news
206 ++++
209 ++++
207
210
208
211
209 fixes
212 fixes
210 +++++
213 +++++
211
214
212 - redirection loop occurs when remember-me wasn't checked during login
215 - redirection loop occurs when remember-me wasn't checked during login
213 - fixes issues with git blob history generation
216 - fixes issues with git blob history generation
214 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
217 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
215
218
216 1.3.0 (**2012-02-26**)
219 1.3.0 (**2012-02-26**)
217 ----------------------
220 ----------------------
218
221
219 news
222 news
220 ++++
223 ++++
221
224
222 - code review, inspired by github code-comments
225 - code review, inspired by github code-comments
223 - #215 rst and markdown README files support
226 - #215 rst and markdown README files support
224 - #252 Container-based and proxy pass-through authentication support
227 - #252 Container-based and proxy pass-through authentication support
225 - #44 branch browser. Filtering of changelog by branches
228 - #44 branch browser. Filtering of changelog by branches
226 - mercurial bookmarks support
229 - mercurial bookmarks support
227 - new hover top menu, optimized to add maximum size for important views
230 - new hover top menu, optimized to add maximum size for important views
228 - configurable clone url template with possibility to specify protocol like
231 - configurable clone url template with possibility to specify protocol like
229 ssh:// or http:// and also manually alter other parts of clone_url.
232 ssh:// or http:// and also manually alter other parts of clone_url.
230 - enabled largefiles extension by default
233 - enabled largefiles extension by default
231 - optimized summary file pages and saved a lot of unused space in them
234 - optimized summary file pages and saved a lot of unused space in them
232 - #239 option to manually mark repository as fork
235 - #239 option to manually mark repository as fork
233 - #320 mapping of commit authors to RhodeCode users
236 - #320 mapping of commit authors to RhodeCode users
234 - #304 hashes are displayed using monospace font
237 - #304 hashes are displayed using monospace font
235 - diff configuration, toggle white lines and context lines
238 - diff configuration, toggle white lines and context lines
236 - #307 configurable diffs, whitespace toggle, increasing context lines
239 - #307 configurable diffs, whitespace toggle, increasing context lines
237 - sorting on branches, tags and bookmarks using YUI datatable
240 - sorting on branches, tags and bookmarks using YUI datatable
238 - improved file filter on files page
241 - improved file filter on files page
239 - implements #330 api method for listing nodes ar particular revision
242 - implements #330 api method for listing nodes ar particular revision
240 - #73 added linking issues in commit messages to chosen issue tracker url
243 - #73 added linking issues in commit messages to chosen issue tracker url
241 based on user defined regular expression
244 based on user defined regular expression
242 - added linking of changesets in commit messages
245 - added linking of changesets in commit messages
243 - new compact changelog with expandable commit messages
246 - new compact changelog with expandable commit messages
244 - firstname and lastname are optional in user creation
247 - firstname and lastname are optional in user creation
245 - #348 added post-create repository hook
248 - #348 added post-create repository hook
246 - #212 global encoding settings is now configurable from .ini files
249 - #212 global encoding settings is now configurable from .ini files
247 - #227 added repository groups permissions
250 - #227 added repository groups permissions
248 - markdown gets codehilite extensions
251 - markdown gets codehilite extensions
249 - new API methods, delete_repositories, grante/revoke permissions for groups
252 - new API methods, delete_repositories, grante/revoke permissions for groups
250 and repos
253 and repos
251
254
252
255
253 fixes
256 fixes
254 +++++
257 +++++
255
258
256 - rewrote dbsession management for atomic operations, and better error handling
259 - rewrote dbsession management for atomic operations, and better error handling
257 - fixed sorting of repo tables
260 - fixed sorting of repo tables
258 - #326 escape of special html entities in diffs
261 - #326 escape of special html entities in diffs
259 - normalized user_name => username in api attributes
262 - normalized user_name => username in api attributes
260 - fixes #298 ldap created users with mixed case emails created conflicts
263 - fixes #298 ldap created users with mixed case emails created conflicts
261 on saving a form
264 on saving a form
262 - fixes issue when owner of a repo couldn't revoke permissions for users
265 - fixes issue when owner of a repo couldn't revoke permissions for users
263 and groups
266 and groups
264 - fixes #271 rare JSON serialization problem with statistics
267 - fixes #271 rare JSON serialization problem with statistics
265 - fixes #337 missing validation check for conflicting names of a group with a
268 - fixes #337 missing validation check for conflicting names of a group with a
266 repositories group
269 repositories group
267 - #340 fixed session problem for mysql and celery tasks
270 - #340 fixed session problem for mysql and celery tasks
268 - fixed #331 RhodeCode mangles repository names if the a repository group
271 - fixed #331 RhodeCode mangles repository names if the a repository group
269 contains the "full path" to the repositories
272 contains the "full path" to the repositories
270 - #355 RhodeCode doesn't store encrypted LDAP passwords
273 - #355 RhodeCode doesn't store encrypted LDAP passwords
271
274
272 1.2.5 (**2012-01-28**)
275 1.2.5 (**2012-01-28**)
273 ----------------------
276 ----------------------
274
277
275 news
278 news
276 ++++
279 ++++
277
280
278 fixes
281 fixes
279 +++++
282 +++++
280
283
281 - #340 Celery complains about MySQL server gone away, added session cleanup
284 - #340 Celery complains about MySQL server gone away, added session cleanup
282 for celery tasks
285 for celery tasks
283 - #341 "scanning for repositories in None" log message during Rescan was missing
286 - #341 "scanning for repositories in None" log message during Rescan was missing
284 a parameter
287 a parameter
285 - fixed creating archives with subrepos. Some hooks were triggered during that
288 - fixed creating archives with subrepos. Some hooks were triggered during that
286 operation leading to crash.
289 operation leading to crash.
287 - fixed missing email in account page.
290 - fixed missing email in account page.
288 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
291 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
289 forking on windows impossible
292 forking on windows impossible
290
293
291 1.2.4 (**2012-01-19**)
294 1.2.4 (**2012-01-19**)
292 ----------------------
295 ----------------------
293
296
294 news
297 news
295 ++++
298 ++++
296
299
297 - RhodeCode is bundled with mercurial series 2.0.X by default, with
300 - RhodeCode is bundled with mercurial series 2.0.X by default, with
298 full support to largefiles extension. Enabled by default in new installations
301 full support to largefiles extension. Enabled by default in new installations
299 - #329 Ability to Add/Remove Groups to/from a Repository via AP
302 - #329 Ability to Add/Remove Groups to/from a Repository via AP
300 - added requires.txt file with requirements
303 - added requires.txt file with requirements
301
304
302 fixes
305 fixes
303 +++++
306 +++++
304
307
305 - fixes db session issues with celery when emailing admins
308 - fixes db session issues with celery when emailing admins
306 - #331 RhodeCode mangles repository names if the a repository group
309 - #331 RhodeCode mangles repository names if the a repository group
307 contains the "full path" to the repositories
310 contains the "full path" to the repositories
308 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
311 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
309 - DB session cleanup after hg protocol operations, fixes issues with
312 - DB session cleanup after hg protocol operations, fixes issues with
310 `mysql has gone away` errors
313 `mysql has gone away` errors
311 - #333 doc fixes for get_repo api function
314 - #333 doc fixes for get_repo api function
312 - #271 rare JSON serialization problem with statistics enabled
315 - #271 rare JSON serialization problem with statistics enabled
313 - #337 Fixes issues with validation of repository name conflicting with
316 - #337 Fixes issues with validation of repository name conflicting with
314 a group name. A proper message is now displayed.
317 a group name. A proper message is now displayed.
315 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
318 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
316 doesn't work
319 doesn't work
317 - #316 fixes issues with web description in hgrc files
320 - #316 fixes issues with web description in hgrc files
318
321
319 1.2.3 (**2011-11-02**)
322 1.2.3 (**2011-11-02**)
320 ----------------------
323 ----------------------
321
324
322 news
325 news
323 ++++
326 ++++
324
327
325 - added option to manage repos group for non admin users
328 - added option to manage repos group for non admin users
326 - added following API methods for get_users, create_user, get_users_groups,
329 - added following API methods for get_users, create_user, get_users_groups,
327 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
330 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
328 get_repo, create_repo, add_user_to_repo
331 get_repo, create_repo, add_user_to_repo
329 - implements #237 added password confirmation for my account
332 - implements #237 added password confirmation for my account
330 and admin edit user.
333 and admin edit user.
331 - implements #291 email notification for global events are now sent to all
334 - implements #291 email notification for global events are now sent to all
332 administrator users, and global config email.
335 administrator users, and global config email.
333
336
334 fixes
337 fixes
335 +++++
338 +++++
336
339
337 - added option for passing auth method for smtp mailer
340 - added option for passing auth method for smtp mailer
338 - #276 issue with adding a single user with id>10 to usergroups
341 - #276 issue with adding a single user with id>10 to usergroups
339 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
342 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
340 - #288 fixes managing of repos in a group for non admin user
343 - #288 fixes managing of repos in a group for non admin user
341
344
342 1.2.2 (**2011-10-17**)
345 1.2.2 (**2011-10-17**)
343 ----------------------
346 ----------------------
344
347
345 news
348 news
346 ++++
349 ++++
347
350
348 - #226 repo groups are available by path instead of numerical id
351 - #226 repo groups are available by path instead of numerical id
349
352
350 fixes
353 fixes
351 +++++
354 +++++
352
355
353 - #259 Groups with the same name but with different parent group
356 - #259 Groups with the same name but with different parent group
354 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
357 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
355 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
358 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
356 - #265 ldap save fails sometimes on converting attributes to booleans,
359 - #265 ldap save fails sometimes on converting attributes to booleans,
357 added getter and setter into model that will prevent from this on db model level
360 added getter and setter into model that will prevent from this on db model level
358 - fixed problems with timestamps issues #251 and #213
361 - fixed problems with timestamps issues #251 and #213
359 - fixes #266 RhodeCode allows to create repo with the same name and in
362 - fixes #266 RhodeCode allows to create repo with the same name and in
360 the same parent as group
363 the same parent as group
361 - fixes #245 Rescan of the repositories on Windows
364 - fixes #245 Rescan of the repositories on Windows
362 - fixes #248 cannot edit repos inside a group on windows
365 - fixes #248 cannot edit repos inside a group on windows
363 - fixes #219 forking problems on windows
366 - fixes #219 forking problems on windows
364
367
365 1.2.1 (**2011-10-08**)
368 1.2.1 (**2011-10-08**)
366 ----------------------
369 ----------------------
367
370
368 news
371 news
369 ++++
372 ++++
370
373
371
374
372 fixes
375 fixes
373 +++++
376 +++++
374
377
375 - fixed problems with basic auth and push problems
378 - fixed problems with basic auth and push problems
376 - gui fixes
379 - gui fixes
377 - fixed logger
380 - fixed logger
378
381
379 1.2.0 (**2011-10-07**)
382 1.2.0 (**2011-10-07**)
380 ----------------------
383 ----------------------
381
384
382 news
385 news
383 ++++
386 ++++
384
387
385 - implemented #47 repository groups
388 - implemented #47 repository groups
386 - implemented #89 Can setup google analytics code from settings menu
389 - implemented #89 Can setup google analytics code from settings menu
387 - implemented #91 added nicer looking archive urls with more download options
390 - implemented #91 added nicer looking archive urls with more download options
388 like tags, branches
391 like tags, branches
389 - implemented #44 into file browsing, and added follow branch option
392 - implemented #44 into file browsing, and added follow branch option
390 - implemented #84 downloads can be enabled/disabled for each repository
393 - implemented #84 downloads can be enabled/disabled for each repository
391 - anonymous repository can be cloned without having to pass default:default
394 - anonymous repository can be cloned without having to pass default:default
392 into clone url
395 into clone url
393 - fixed #90 whoosh indexer can index chooses repositories passed in command
396 - fixed #90 whoosh indexer can index chooses repositories passed in command
394 line
397 line
395 - extended journal with day aggregates and paging
398 - extended journal with day aggregates and paging
396 - implemented #107 source code lines highlight ranges
399 - implemented #107 source code lines highlight ranges
397 - implemented #93 customizable changelog on combined revision ranges -
400 - implemented #93 customizable changelog on combined revision ranges -
398 equivalent of githubs compare view
401 equivalent of githubs compare view
399 - implemented #108 extended and more powerful LDAP configuration
402 - implemented #108 extended and more powerful LDAP configuration
400 - implemented #56 users groups
403 - implemented #56 users groups
401 - major code rewrites optimized codes for speed and memory usage
404 - major code rewrites optimized codes for speed and memory usage
402 - raw and diff downloads are now in git format
405 - raw and diff downloads are now in git format
403 - setup command checks for write access to given path
406 - setup command checks for write access to given path
404 - fixed many issues with international characters and unicode. It uses utf8
407 - fixed many issues with international characters and unicode. It uses utf8
405 decode with replace to provide less errors even with non utf8 encoded strings
408 decode with replace to provide less errors even with non utf8 encoded strings
406 - #125 added API KEY access to feeds
409 - #125 added API KEY access to feeds
407 - #109 Repository can be created from external Mercurial link (aka. remote
410 - #109 Repository can be created from external Mercurial link (aka. remote
408 repository, and manually updated (via pull) from admin panel
411 repository, and manually updated (via pull) from admin panel
409 - beta git support - push/pull server + basic view for git repos
412 - beta git support - push/pull server + basic view for git repos
410 - added followers page and forks page
413 - added followers page and forks page
411 - server side file creation (with binary file upload interface)
414 - server side file creation (with binary file upload interface)
412 and edition with commits powered by codemirror
415 and edition with commits powered by codemirror
413 - #111 file browser file finder, quick lookup files on whole file tree
416 - #111 file browser file finder, quick lookup files on whole file tree
414 - added quick login sliding menu into main page
417 - added quick login sliding menu into main page
415 - changelog uses lazy loading of affected files details, in some scenarios
418 - changelog uses lazy loading of affected files details, in some scenarios
416 this can improve speed of changelog page dramatically especially for
419 this can improve speed of changelog page dramatically especially for
417 larger repositories.
420 larger repositories.
418 - implements #214 added support for downloading subrepos in download menu.
421 - implements #214 added support for downloading subrepos in download menu.
419 - Added basic API for direct operations on rhodecode via JSON
422 - Added basic API for direct operations on rhodecode via JSON
420 - Implemented advanced hook management
423 - Implemented advanced hook management
421
424
422 fixes
425 fixes
423 +++++
426 +++++
424
427
425 - fixed file browser bug, when switching into given form revision the url was
428 - fixed file browser bug, when switching into given form revision the url was
426 not changing
429 not changing
427 - fixed propagation to error controller on simplehg and simplegit middlewares
430 - fixed propagation to error controller on simplehg and simplegit middlewares
428 - fixed error when trying to make a download on empty repository
431 - fixed error when trying to make a download on empty repository
429 - fixed problem with '[' chars in commit messages in journal
432 - fixed problem with '[' chars in commit messages in journal
430 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
433 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
431 - journal fork fixes
434 - journal fork fixes
432 - removed issue with space inside renamed repository after deletion
435 - removed issue with space inside renamed repository after deletion
433 - fixed strange issue on formencode imports
436 - fixed strange issue on formencode imports
434 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
437 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
435 - #150 fixes for errors on repositories mapped in db but corrupted in
438 - #150 fixes for errors on repositories mapped in db but corrupted in
436 filesystem
439 filesystem
437 - fixed problem with ascendant characters in realm #181
440 - fixed problem with ascendant characters in realm #181
438 - fixed problem with sqlite file based database connection pool
441 - fixed problem with sqlite file based database connection pool
439 - whoosh indexer and code stats share the same dynamic extensions map
442 - whoosh indexer and code stats share the same dynamic extensions map
440 - fixes #188 - relationship delete of repo_to_perm entry on user removal
443 - fixes #188 - relationship delete of repo_to_perm entry on user removal
441 - fixes issue #189 Trending source files shows "show more" when no more exist
444 - fixes issue #189 Trending source files shows "show more" when no more exist
442 - fixes issue #197 Relative paths for pidlocks
445 - fixes issue #197 Relative paths for pidlocks
443 - fixes issue #198 password will require only 3 chars now for login form
446 - fixes issue #198 password will require only 3 chars now for login form
444 - fixes issue #199 wrong redirection for non admin users after creating a repository
447 - fixes issue #199 wrong redirection for non admin users after creating a repository
445 - fixes issues #202, bad db constraint made impossible to attach same group
448 - fixes issues #202, bad db constraint made impossible to attach same group
446 more than one time. Affects only mysql/postgres
449 more than one time. Affects only mysql/postgres
447 - fixes #218 os.kill patch for windows was missing sig param
450 - fixes #218 os.kill patch for windows was missing sig param
448 - improved rendering of dag (they are not trimmed anymore when number of
451 - improved rendering of dag (they are not trimmed anymore when number of
449 heads exceeds 5)
452 heads exceeds 5)
450
453
451 1.1.8 (**2011-04-12**)
454 1.1.8 (**2011-04-12**)
452 ----------------------
455 ----------------------
453
456
454 news
457 news
455 ++++
458 ++++
456
459
457 - improved windows support
460 - improved windows support
458
461
459 fixes
462 fixes
460 +++++
463 +++++
461
464
462 - fixed #140 freeze of python dateutil library, since new version is python2.x
465 - fixed #140 freeze of python dateutil library, since new version is python2.x
463 incompatible
466 incompatible
464 - setup-app will check for write permission in given path
467 - setup-app will check for write permission in given path
465 - cleaned up license info issue #149
468 - cleaned up license info issue #149
466 - fixes for issues #137,#116 and problems with unicode and accented characters.
469 - fixes for issues #137,#116 and problems with unicode and accented characters.
467 - fixes crashes on gravatar, when passed in email as unicode
470 - fixes crashes on gravatar, when passed in email as unicode
468 - fixed tooltip flickering problems
471 - fixed tooltip flickering problems
469 - fixed came_from redirection on windows
472 - fixed came_from redirection on windows
470 - fixed logging modules, and sql formatters
473 - fixed logging modules, and sql formatters
471 - windows fixes for os.kill issue #133
474 - windows fixes for os.kill issue #133
472 - fixes path splitting for windows issues #148
475 - fixes path splitting for windows issues #148
473 - fixed issue #143 wrong import on migration to 1.1.X
476 - fixed issue #143 wrong import on migration to 1.1.X
474 - fixed problems with displaying binary files, thanks to Thomas Waldmann
477 - fixed problems with displaying binary files, thanks to Thomas Waldmann
475 - removed name from archive files since it's breaking ui for long repo names
478 - removed name from archive files since it's breaking ui for long repo names
476 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
479 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
477 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
480 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
478 Thomas Waldmann
481 Thomas Waldmann
479 - fixed issue #166 summary pager was skipping 10 revisions on second page
482 - fixed issue #166 summary pager was skipping 10 revisions on second page
480
483
481
484
482 1.1.7 (**2011-03-23**)
485 1.1.7 (**2011-03-23**)
483 ----------------------
486 ----------------------
484
487
485 news
488 news
486 ++++
489 ++++
487
490
488 fixes
491 fixes
489 +++++
492 +++++
490
493
491 - fixed (again) #136 installation support for FreeBSD
494 - fixed (again) #136 installation support for FreeBSD
492
495
493
496
494 1.1.6 (**2011-03-21**)
497 1.1.6 (**2011-03-21**)
495 ----------------------
498 ----------------------
496
499
497 news
500 news
498 ++++
501 ++++
499
502
500 fixes
503 fixes
501 +++++
504 +++++
502
505
503 - fixed #136 installation support for FreeBSD
506 - fixed #136 installation support for FreeBSD
504 - RhodeCode will check for python version during installation
507 - RhodeCode will check for python version during installation
505
508
506 1.1.5 (**2011-03-17**)
509 1.1.5 (**2011-03-17**)
507 ----------------------
510 ----------------------
508
511
509 news
512 news
510 ++++
513 ++++
511
514
512 - basic windows support, by exchanging pybcrypt into sha256 for windows only
515 - basic windows support, by exchanging pybcrypt into sha256 for windows only
513 highly inspired by idea of mantis406
516 highly inspired by idea of mantis406
514
517
515 fixes
518 fixes
516 +++++
519 +++++
517
520
518 - fixed sorting by author in main page
521 - fixed sorting by author in main page
519 - fixed crashes with diffs on binary files
522 - fixed crashes with diffs on binary files
520 - fixed #131 problem with boolean values for LDAP
523 - fixed #131 problem with boolean values for LDAP
521 - fixed #122 mysql problems thanks to striker69
524 - fixed #122 mysql problems thanks to striker69
522 - fixed problem with errors on calling raw/raw_files/annotate functions
525 - fixed problem with errors on calling raw/raw_files/annotate functions
523 with unknown revisions
526 with unknown revisions
524 - fixed returned rawfiles attachment names with international character
527 - fixed returned rawfiles attachment names with international character
525 - cleaned out docs, big thanks to Jason Harris
528 - cleaned out docs, big thanks to Jason Harris
526
529
527 1.1.4 (**2011-02-19**)
530 1.1.4 (**2011-02-19**)
528 ----------------------
531 ----------------------
529
532
530 news
533 news
531 ++++
534 ++++
532
535
533 fixes
536 fixes
534 +++++
537 +++++
535
538
536 - fixed formencode import problem on settings page, that caused server crash
539 - fixed formencode import problem on settings page, that caused server crash
537 when that page was accessed as first after server start
540 when that page was accessed as first after server start
538 - journal fixes
541 - journal fixes
539 - fixed option to access repository just by entering http://server/<repo_name>
542 - fixed option to access repository just by entering http://server/<repo_name>
540
543
541 1.1.3 (**2011-02-16**)
544 1.1.3 (**2011-02-16**)
542 ----------------------
545 ----------------------
543
546
544 news
547 news
545 ++++
548 ++++
546
549
547 - implemented #102 allowing the '.' character in username
550 - implemented #102 allowing the '.' character in username
548 - added option to access repository just by entering http://server/<repo_name>
551 - added option to access repository just by entering http://server/<repo_name>
549 - celery task ignores result for better performance
552 - celery task ignores result for better performance
550
553
551 fixes
554 fixes
552 +++++
555 +++++
553
556
554 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
557 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
555 apollo13 and Johan Walles
558 apollo13 and Johan Walles
556 - small fixes in journal
559 - small fixes in journal
557 - fixed problems with getting setting for celery from .ini files
560 - fixed problems with getting setting for celery from .ini files
558 - registration, password reset and login boxes share the same title as main
561 - registration, password reset and login boxes share the same title as main
559 application now
562 application now
560 - fixed #113: to high permissions to fork repository
563 - fixed #113: to high permissions to fork repository
561 - fixed problem with '[' chars in commit messages in journal
564 - fixed problem with '[' chars in commit messages in journal
562 - removed issue with space inside renamed repository after deletion
565 - removed issue with space inside renamed repository after deletion
563 - db transaction fixes when filesystem repository creation failed
566 - db transaction fixes when filesystem repository creation failed
564 - fixed #106 relation issues on databases different than sqlite
567 - fixed #106 relation issues on databases different than sqlite
565 - fixed static files paths links to use of url() method
568 - fixed static files paths links to use of url() method
566
569
567 1.1.2 (**2011-01-12**)
570 1.1.2 (**2011-01-12**)
568 ----------------------
571 ----------------------
569
572
570 news
573 news
571 ++++
574 ++++
572
575
573
576
574 fixes
577 fixes
575 +++++
578 +++++
576
579
577 - fixes #98 protection against float division of percentage stats
580 - fixes #98 protection against float division of percentage stats
578 - fixed graph bug
581 - fixed graph bug
579 - forced webhelpers version since it was making troubles during installation
582 - forced webhelpers version since it was making troubles during installation
580
583
581 1.1.1 (**2011-01-06**)
584 1.1.1 (**2011-01-06**)
582 ----------------------
585 ----------------------
583
586
584 news
587 news
585 ++++
588 ++++
586
589
587 - added force https option into ini files for easier https usage (no need to
590 - added force https option into ini files for easier https usage (no need to
588 set server headers with this options)
591 set server headers with this options)
589 - small css updates
592 - small css updates
590
593
591 fixes
594 fixes
592 +++++
595 +++++
593
596
594 - fixed #96 redirect loop on files view on repositories without changesets
597 - fixed #96 redirect loop on files view on repositories without changesets
595 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
598 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
596 and server crashed with errors
599 and server crashed with errors
597 - fixed large tooltips problems on main page
600 - fixed large tooltips problems on main page
598 - fixed #92 whoosh indexer is more error proof
601 - fixed #92 whoosh indexer is more error proof
599
602
600 1.1.0 (**2010-12-18**)
603 1.1.0 (**2010-12-18**)
601 ----------------------
604 ----------------------
602
605
603 news
606 news
604 ++++
607 ++++
605
608
606 - rewrite of internals for vcs >=0.1.10
609 - rewrite of internals for vcs >=0.1.10
607 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
610 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
608 with older clients
611 with older clients
609 - anonymous access, authentication via ldap
612 - anonymous access, authentication via ldap
610 - performance upgrade for cached repos list - each repository has its own
613 - performance upgrade for cached repos list - each repository has its own
611 cache that's invalidated when needed.
614 cache that's invalidated when needed.
612 - performance upgrades on repositories with large amount of commits (20K+)
615 - performance upgrades on repositories with large amount of commits (20K+)
613 - main page quick filter for filtering repositories
616 - main page quick filter for filtering repositories
614 - user dashboards with ability to follow chosen repositories actions
617 - user dashboards with ability to follow chosen repositories actions
615 - sends email to admin on new user registration
618 - sends email to admin on new user registration
616 - added cache/statistics reset options into repository settings
619 - added cache/statistics reset options into repository settings
617 - more detailed action logger (based on hooks) with pushed changesets lists
620 - more detailed action logger (based on hooks) with pushed changesets lists
618 and options to disable those hooks from admin panel
621 and options to disable those hooks from admin panel
619 - introduced new enhanced changelog for merges that shows more accurate results
622 - introduced new enhanced changelog for merges that shows more accurate results
620 - new improved and faster code stats (based on pygments lexers mapping tables,
623 - new improved and faster code stats (based on pygments lexers mapping tables,
621 showing up to 10 trending sources for each repository. Additionally stats
624 showing up to 10 trending sources for each repository. Additionally stats
622 can be disabled in repository settings.
625 can be disabled in repository settings.
623 - gui optimizations, fixed application width to 1024px
626 - gui optimizations, fixed application width to 1024px
624 - added cut off (for large files/changesets) limit into config files
627 - added cut off (for large files/changesets) limit into config files
625 - whoosh, celeryd, upgrade moved to paster command
628 - whoosh, celeryd, upgrade moved to paster command
626 - other than sqlite database backends can be used
629 - other than sqlite database backends can be used
627
630
628 fixes
631 fixes
629 +++++
632 +++++
630
633
631 - fixes #61 forked repo was showing only after cache expired
634 - fixes #61 forked repo was showing only after cache expired
632 - fixes #76 no confirmation on user deletes
635 - fixes #76 no confirmation on user deletes
633 - fixes #66 Name field misspelled
636 - fixes #66 Name field misspelled
634 - fixes #72 block user removal when he owns repositories
637 - fixes #72 block user removal when he owns repositories
635 - fixes #69 added password confirmation fields
638 - fixes #69 added password confirmation fields
636 - fixes #87 RhodeCode crashes occasionally on updating repository owner
639 - fixes #87 RhodeCode crashes occasionally on updating repository owner
637 - fixes #82 broken annotations on files with more than 1 blank line at the end
640 - fixes #82 broken annotations on files with more than 1 blank line at the end
638 - a lot of fixes and tweaks for file browser
641 - a lot of fixes and tweaks for file browser
639 - fixed detached session issues
642 - fixed detached session issues
640 - fixed when user had no repos he would see all repos listed in my account
643 - fixed when user had no repos he would see all repos listed in my account
641 - fixed ui() instance bug when global hgrc settings was loaded for server
644 - fixed ui() instance bug when global hgrc settings was loaded for server
642 instance and all hgrc options were merged with our db ui() object
645 instance and all hgrc options were merged with our db ui() object
643 - numerous small bugfixes
646 - numerous small bugfixes
644
647
645 (special thanks for TkSoh for detailed feedback)
648 (special thanks for TkSoh for detailed feedback)
646
649
647
650
648 1.0.2 (**2010-11-12**)
651 1.0.2 (**2010-11-12**)
649 ----------------------
652 ----------------------
650
653
651 news
654 news
652 ++++
655 ++++
653
656
654 - tested under python2.7
657 - tested under python2.7
655 - bumped sqlalchemy and celery versions
658 - bumped sqlalchemy and celery versions
656
659
657 fixes
660 fixes
658 +++++
661 +++++
659
662
660 - fixed #59 missing graph.js
663 - fixed #59 missing graph.js
661 - fixed repo_size crash when repository had broken symlinks
664 - fixed repo_size crash when repository had broken symlinks
662 - fixed python2.5 crashes.
665 - fixed python2.5 crashes.
663
666
664
667
665 1.0.1 (**2010-11-10**)
668 1.0.1 (**2010-11-10**)
666 ----------------------
669 ----------------------
667
670
668 news
671 news
669 ++++
672 ++++
670
673
671 - small css updated
674 - small css updated
672
675
673 fixes
676 fixes
674 +++++
677 +++++
675
678
676 - fixed #53 python2.5 incompatible enumerate calls
679 - fixed #53 python2.5 incompatible enumerate calls
677 - fixed #52 disable mercurial extension for web
680 - fixed #52 disable mercurial extension for web
678 - fixed #51 deleting repositories don't delete it's dependent objects
681 - fixed #51 deleting repositories don't delete it's dependent objects
679
682
680
683
681 1.0.0 (**2010-11-02**)
684 1.0.0 (**2010-11-02**)
682 ----------------------
685 ----------------------
683
686
684 - security bugfix simplehg wasn't checking for permissions on commands
687 - security bugfix simplehg wasn't checking for permissions on commands
685 other than pull or push.
688 other than pull or push.
686 - fixed doubled messages after push or pull in admin journal
689 - fixed doubled messages after push or pull in admin journal
687 - templating and css corrections, fixed repo switcher on chrome, updated titles
690 - templating and css corrections, fixed repo switcher on chrome, updated titles
688 - admin menu accessible from options menu on repository view
691 - admin menu accessible from options menu on repository view
689 - permissions cached queries
692 - permissions cached queries
690
693
691 1.0.0rc4 (**2010-10-12**)
694 1.0.0rc4 (**2010-10-12**)
692 --------------------------
695 --------------------------
693
696
694 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
697 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
695 - removed cache_manager settings from sqlalchemy meta
698 - removed cache_manager settings from sqlalchemy meta
696 - added sqlalchemy cache settings to ini files
699 - added sqlalchemy cache settings to ini files
697 - validated password length and added second try of failure on paster setup-app
700 - validated password length and added second try of failure on paster setup-app
698 - fixed setup database destroy prompt even when there was no db
701 - fixed setup database destroy prompt even when there was no db
699
702
700
703
701 1.0.0rc3 (**2010-10-11**)
704 1.0.0rc3 (**2010-10-11**)
702 -------------------------
705 -------------------------
703
706
704 - fixed i18n during installation.
707 - fixed i18n during installation.
705
708
706 1.0.0rc2 (**2010-10-11**)
709 1.0.0rc2 (**2010-10-11**)
707 -------------------------
710 -------------------------
708
711
709 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
712 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
710 occure. After vcs is fixed it'll be put back again.
713 occure. After vcs is fixed it'll be put back again.
711 - templating/css rewrites, optimized css. No newline at end of file
714 - templating/css rewrites, optimized css.
@@ -1,467 +1,466 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import re
26 import re
27 import time
27 import time
28 import datetime
28 import datetime
29 from pylons.i18n.translation import _, ungettext
29 from pylons.i18n.translation import _, ungettext
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
31
31
32
32
33 def __get_lem():
33 def __get_lem():
34 """
34 """
35 Get language extension map based on what's inside pygments lexers
35 Get language extension map based on what's inside pygments lexers
36 """
36 """
37 from pygments import lexers
37 from pygments import lexers
38 from string import lower
38 from string import lower
39 from collections import defaultdict
39 from collections import defaultdict
40
40
41 d = defaultdict(lambda: [])
41 d = defaultdict(lambda: [])
42
42
43 def __clean(s):
43 def __clean(s):
44 s = s.lstrip('*')
44 s = s.lstrip('*')
45 s = s.lstrip('.')
45 s = s.lstrip('.')
46
46
47 if s.find('[') != -1:
47 if s.find('[') != -1:
48 exts = []
48 exts = []
49 start, stop = s.find('['), s.find(']')
49 start, stop = s.find('['), s.find(']')
50
50
51 for suffix in s[start + 1:stop]:
51 for suffix in s[start + 1:stop]:
52 exts.append(s[:s.find('[')] + suffix)
52 exts.append(s[:s.find('[')] + suffix)
53 return map(lower, exts)
53 return map(lower, exts)
54 else:
54 else:
55 return map(lower, [s])
55 return map(lower, [s])
56
56
57 for lx, t in sorted(lexers.LEXERS.items()):
57 for lx, t in sorted(lexers.LEXERS.items()):
58 m = map(__clean, t[-2])
58 m = map(__clean, t[-2])
59 if m:
59 if m:
60 m = reduce(lambda x, y: x + y, m)
60 m = reduce(lambda x, y: x + y, m)
61 for ext in m:
61 for ext in m:
62 desc = lx.replace('Lexer', '')
62 desc = lx.replace('Lexer', '')
63 d[ext].append(desc)
63 d[ext].append(desc)
64
64
65 return dict(d)
65 return dict(d)
66
66
67 def str2bool(_str):
67 def str2bool(_str):
68 """
68 """
69 returs True/False value from given string, it tries to translate the
69 returs True/False value from given string, it tries to translate the
70 string into boolean
70 string into boolean
71
71
72 :param _str: string value to translate into boolean
72 :param _str: string value to translate into boolean
73 :rtype: boolean
73 :rtype: boolean
74 :returns: boolean from given string
74 :returns: boolean from given string
75 """
75 """
76 if _str is None:
76 if _str is None:
77 return False
77 return False
78 if _str in (True, False):
78 if _str in (True, False):
79 return _str
79 return _str
80 _str = str(_str).strip().lower()
80 _str = str(_str).strip().lower()
81 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
81 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
82
82
83
83
84 def convert_line_endings(line, mode):
84 def convert_line_endings(line, mode):
85 """
85 """
86 Converts a given line "line end" accordingly to given mode
86 Converts a given line "line end" accordingly to given mode
87
87
88 Available modes are::
88 Available modes are::
89 0 - Unix
89 0 - Unix
90 1 - Mac
90 1 - Mac
91 2 - DOS
91 2 - DOS
92
92
93 :param line: given line to convert
93 :param line: given line to convert
94 :param mode: mode to convert to
94 :param mode: mode to convert to
95 :rtype: str
95 :rtype: str
96 :return: converted line according to mode
96 :return: converted line according to mode
97 """
97 """
98 from string import replace
98 from string import replace
99
99
100 if mode == 0:
100 if mode == 0:
101 line = replace(line, '\r\n', '\n')
101 line = replace(line, '\r\n', '\n')
102 line = replace(line, '\r', '\n')
102 line = replace(line, '\r', '\n')
103 elif mode == 1:
103 elif mode == 1:
104 line = replace(line, '\r\n', '\r')
104 line = replace(line, '\r\n', '\r')
105 line = replace(line, '\n', '\r')
105 line = replace(line, '\n', '\r')
106 elif mode == 2:
106 elif mode == 2:
107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
108 return line
108 return line
109
109
110
110
111 def detect_mode(line, default):
111 def detect_mode(line, default):
112 """
112 """
113 Detects line break for given line, if line break couldn't be found
113 Detects line break for given line, if line break couldn't be found
114 given default value is returned
114 given default value is returned
115
115
116 :param line: str line
116 :param line: str line
117 :param default: default
117 :param default: default
118 :rtype: int
118 :rtype: int
119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
120 """
120 """
121 if line.endswith('\r\n'):
121 if line.endswith('\r\n'):
122 return 2
122 return 2
123 elif line.endswith('\n'):
123 elif line.endswith('\n'):
124 return 0
124 return 0
125 elif line.endswith('\r'):
125 elif line.endswith('\r'):
126 return 1
126 return 1
127 else:
127 else:
128 return default
128 return default
129
129
130
130
131 def generate_api_key(username, salt=None):
131 def generate_api_key(username, salt=None):
132 """
132 """
133 Generates unique API key for given username, if salt is not given
133 Generates unique API key for given username, if salt is not given
134 it'll be generated from some random string
134 it'll be generated from some random string
135
135
136 :param username: username as string
136 :param username: username as string
137 :param salt: salt to hash generate KEY
137 :param salt: salt to hash generate KEY
138 :rtype: str
138 :rtype: str
139 :returns: sha1 hash from username+salt
139 :returns: sha1 hash from username+salt
140 """
140 """
141 from tempfile import _RandomNameSequence
141 from tempfile import _RandomNameSequence
142 import hashlib
142 import hashlib
143
143
144 if salt is None:
144 if salt is None:
145 salt = _RandomNameSequence().next()
145 salt = _RandomNameSequence().next()
146
146
147 return hashlib.sha1(username + salt).hexdigest()
147 return hashlib.sha1(username + salt).hexdigest()
148
148
149
149
150 def safe_unicode(str_, from_encoding=None):
150 def safe_unicode(str_, from_encoding=None):
151 """
151 """
152 safe unicode function. Does few trick to turn str_ into unicode
152 safe unicode function. Does few trick to turn str_ into unicode
153
153
154 In case of UnicodeDecode error we try to return it with encoding detected
154 In case of UnicodeDecode error we try to return it with encoding detected
155 by chardet library if it fails fallback to unicode with errors replaced
155 by chardet library if it fails fallback to unicode with errors replaced
156
156
157 :param str_: string to decode
157 :param str_: string to decode
158 :rtype: unicode
158 :rtype: unicode
159 :returns: unicode object
159 :returns: unicode object
160 """
160 """
161 if isinstance(str_, unicode):
161 if isinstance(str_, unicode):
162 return str_
162 return str_
163
163
164 if not from_encoding:
164 if not from_encoding:
165 import rhodecode
165 import rhodecode
166 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
166 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
167 from_encoding = DEFAULT_ENCODING
167 from_encoding = DEFAULT_ENCODING
168
168
169 try:
169 try:
170 return unicode(str_)
170 return unicode(str_)
171 except UnicodeDecodeError:
171 except UnicodeDecodeError:
172 pass
172 pass
173
173
174 try:
174 try:
175 return unicode(str_, from_encoding)
175 return unicode(str_, from_encoding)
176 except UnicodeDecodeError:
176 except UnicodeDecodeError:
177 pass
177 pass
178
178
179 try:
179 try:
180 import chardet
180 import chardet
181 encoding = chardet.detect(str_)['encoding']
181 encoding = chardet.detect(str_)['encoding']
182 if encoding is None:
182 if encoding is None:
183 raise Exception()
183 raise Exception()
184 return str_.decode(encoding)
184 return str_.decode(encoding)
185 except (ImportError, UnicodeDecodeError, Exception):
185 except (ImportError, UnicodeDecodeError, Exception):
186 return unicode(str_, from_encoding, 'replace')
186 return unicode(str_, from_encoding, 'replace')
187
187
188
188
189 def safe_str(unicode_, to_encoding=None):
189 def safe_str(unicode_, to_encoding=None):
190 """
190 """
191 safe str function. Does few trick to turn unicode_ into string
191 safe str function. Does few trick to turn unicode_ into string
192
192
193 In case of UnicodeEncodeError we try to return it with encoding detected
193 In case of UnicodeEncodeError we try to return it with encoding detected
194 by chardet library if it fails fallback to string with errors replaced
194 by chardet library if it fails fallback to string with errors replaced
195
195
196 :param unicode_: unicode to encode
196 :param unicode_: unicode to encode
197 :rtype: str
197 :rtype: str
198 :returns: str object
198 :returns: str object
199 """
199 """
200
200
201 # if it's not basestr cast to str
201 # if it's not basestr cast to str
202 if not isinstance(unicode_, basestring):
202 if not isinstance(unicode_, basestring):
203 return str(unicode_)
203 return str(unicode_)
204
204
205 if isinstance(unicode_, str):
205 if isinstance(unicode_, str):
206 return unicode_
206 return unicode_
207
207
208 if not to_encoding:
208 if not to_encoding:
209 import rhodecode
209 import rhodecode
210 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
210 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
211 to_encoding = DEFAULT_ENCODING
211 to_encoding = DEFAULT_ENCODING
212
212
213 try:
213 try:
214 return unicode_.encode(to_encoding)
214 return unicode_.encode(to_encoding)
215 except UnicodeEncodeError:
215 except UnicodeEncodeError:
216 pass
216 pass
217
217
218 try:
218 try:
219 import chardet
219 import chardet
220 encoding = chardet.detect(unicode_)['encoding']
220 encoding = chardet.detect(unicode_)['encoding']
221 if encoding is None:
221 if encoding is None:
222 raise UnicodeEncodeError()
222 raise UnicodeEncodeError()
223
223
224 return unicode_.encode(encoding)
224 return unicode_.encode(encoding)
225 except (ImportError, UnicodeEncodeError):
225 except (ImportError, UnicodeEncodeError):
226 return unicode_.encode(to_encoding, 'replace')
226 return unicode_.encode(to_encoding, 'replace')
227
227
228 return safe_str
228 return safe_str
229
229
230
230
231 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
231 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
232 """
232 """
233 Custom engine_from_config functions that makes sure we use NullPool for
233 Custom engine_from_config functions that makes sure we use NullPool for
234 file based sqlite databases. This prevents errors on sqlite. This only
234 file based sqlite databases. This prevents errors on sqlite. This only
235 applies to sqlalchemy versions < 0.7.0
235 applies to sqlalchemy versions < 0.7.0
236
236
237 """
237 """
238 import sqlalchemy
238 import sqlalchemy
239 from sqlalchemy import engine_from_config as efc
239 from sqlalchemy import engine_from_config as efc
240 import logging
240 import logging
241
241
242 if int(sqlalchemy.__version__.split('.')[1]) < 7:
242 if int(sqlalchemy.__version__.split('.')[1]) < 7:
243
243
244 # This solution should work for sqlalchemy < 0.7.0, and should use
244 # This solution should work for sqlalchemy < 0.7.0, and should use
245 # proxy=TimerProxy() for execution time profiling
245 # proxy=TimerProxy() for execution time profiling
246
246
247 from sqlalchemy.pool import NullPool
247 from sqlalchemy.pool import NullPool
248 url = configuration[prefix + 'url']
248 url = configuration[prefix + 'url']
249
249
250 if url.startswith('sqlite'):
250 if url.startswith('sqlite'):
251 kwargs.update({'poolclass': NullPool})
251 kwargs.update({'poolclass': NullPool})
252 return efc(configuration, prefix, **kwargs)
252 return efc(configuration, prefix, **kwargs)
253 else:
253 else:
254 import time
254 import time
255 from sqlalchemy import event
255 from sqlalchemy import event
256 from sqlalchemy.engine import Engine
256 from sqlalchemy.engine import Engine
257
257
258 log = logging.getLogger('sqlalchemy.engine')
258 log = logging.getLogger('sqlalchemy.engine')
259 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
259 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
260 engine = efc(configuration, prefix, **kwargs)
260 engine = efc(configuration, prefix, **kwargs)
261
261
262 def color_sql(sql):
262 def color_sql(sql):
263 COLOR_SEQ = "\033[1;%dm"
263 COLOR_SEQ = "\033[1;%dm"
264 COLOR_SQL = YELLOW
264 COLOR_SQL = YELLOW
265 normal = '\x1b[0m'
265 normal = '\x1b[0m'
266 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
266 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
267
267
268 if configuration['debug']:
268 if configuration['debug']:
269 #attach events only for debug configuration
269 #attach events only for debug configuration
270
270
271 def before_cursor_execute(conn, cursor, statement,
271 def before_cursor_execute(conn, cursor, statement,
272 parameters, context, executemany):
272 parameters, context, executemany):
273 context._query_start_time = time.time()
273 context._query_start_time = time.time()
274 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
274 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
275
275
276
277 def after_cursor_execute(conn, cursor, statement,
276 def after_cursor_execute(conn, cursor, statement,
278 parameters, context, executemany):
277 parameters, context, executemany):
279 total = time.time() - context._query_start_time
278 total = time.time() - context._query_start_time
280 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
279 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
281
280
282 event.listen(engine, "before_cursor_execute",
281 event.listen(engine, "before_cursor_execute",
283 before_cursor_execute)
282 before_cursor_execute)
284 event.listen(engine, "after_cursor_execute",
283 event.listen(engine, "after_cursor_execute",
285 after_cursor_execute)
284 after_cursor_execute)
286
285
287 return engine
286 return engine
288
287
289
288
290 def age(prevdate):
289 def age(prevdate):
291 """
290 """
292 turns a datetime into an age string.
291 turns a datetime into an age string.
293
292
294 :param prevdate: datetime object
293 :param prevdate: datetime object
295 :rtype: unicode
294 :rtype: unicode
296 :returns: unicode words describing age
295 :returns: unicode words describing age
297 """
296 """
298
297
299 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
298 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
300 deltas = {}
299 deltas = {}
301
300
302 # Get date parts deltas
301 # Get date parts deltas
303 now = datetime.datetime.now()
302 now = datetime.datetime.now()
304 for part in order:
303 for part in order:
305 deltas[part] = getattr(now, part) - getattr(prevdate, part)
304 deltas[part] = getattr(now, part) - getattr(prevdate, part)
306
305
307 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
306 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
308 # not 1 hour, -59 minutes and -59 seconds)
307 # not 1 hour, -59 minutes and -59 seconds)
309
308
310 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
309 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
311 part = order[num]
310 part = order[num]
312 carry_part = order[num - 1]
311 carry_part = order[num - 1]
313
312
314 if deltas[part] < 0:
313 if deltas[part] < 0:
315 deltas[part] += length
314 deltas[part] += length
316 deltas[carry_part] -= 1
315 deltas[carry_part] -= 1
317
316
318 # Same thing for days except that the increment depends on the (variable)
317 # Same thing for days except that the increment depends on the (variable)
319 # number of days in the month
318 # number of days in the month
320 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
319 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
321 if deltas['day'] < 0:
320 if deltas['day'] < 0:
322 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
321 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
323 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
322 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
324 deltas['day'] += 29
323 deltas['day'] += 29
325 else:
324 else:
326 deltas['day'] += month_lengths[prevdate.month - 1]
325 deltas['day'] += month_lengths[prevdate.month - 1]
327
326
328 deltas['month'] -= 1
327 deltas['month'] -= 1
329
328
330 if deltas['month'] < 0:
329 if deltas['month'] < 0:
331 deltas['month'] += 12
330 deltas['month'] += 12
332 deltas['year'] -= 1
331 deltas['year'] -= 1
333
332
334 # Format the result
333 # Format the result
335 fmt_funcs = {
334 fmt_funcs = {
336 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
335 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
337 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
336 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
338 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
337 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
339 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
338 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
340 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
339 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
341 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
340 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
342 }
341 }
343
342
344 for i, part in enumerate(order):
343 for i, part in enumerate(order):
345 value = deltas[part]
344 value = deltas[part]
346 if value == 0:
345 if value == 0:
347 continue
346 continue
348
347
349 if i < 5:
348 if i < 5:
350 sub_part = order[i + 1]
349 sub_part = order[i + 1]
351 sub_value = deltas[sub_part]
350 sub_value = deltas[sub_part]
352 else:
351 else:
353 sub_value = 0
352 sub_value = 0
354
353
355 if sub_value == 0:
354 if sub_value == 0:
356 return _(u'%s ago') % fmt_funcs[part](value)
355 return _(u'%s ago') % fmt_funcs[part](value)
357
356
358 return _(u'%s and %s ago') % (fmt_funcs[part](value),
357 return _(u'%s and %s ago') % (fmt_funcs[part](value),
359 fmt_funcs[sub_part](sub_value))
358 fmt_funcs[sub_part](sub_value))
360
359
361 return _(u'just now')
360 return _(u'just now')
362
361
363
362
364 def uri_filter(uri):
363 def uri_filter(uri):
365 """
364 """
366 Removes user:password from given url string
365 Removes user:password from given url string
367
366
368 :param uri:
367 :param uri:
369 :rtype: unicode
368 :rtype: unicode
370 :returns: filtered list of strings
369 :returns: filtered list of strings
371 """
370 """
372 if not uri:
371 if not uri:
373 return ''
372 return ''
374
373
375 proto = ''
374 proto = ''
376
375
377 for pat in ('https://', 'http://'):
376 for pat in ('https://', 'http://'):
378 if uri.startswith(pat):
377 if uri.startswith(pat):
379 uri = uri[len(pat):]
378 uri = uri[len(pat):]
380 proto = pat
379 proto = pat
381 break
380 break
382
381
383 # remove passwords and username
382 # remove passwords and username
384 uri = uri[uri.find('@') + 1:]
383 uri = uri[uri.find('@') + 1:]
385
384
386 # get the port
385 # get the port
387 cred_pos = uri.find(':')
386 cred_pos = uri.find(':')
388 if cred_pos == -1:
387 if cred_pos == -1:
389 host, port = uri, None
388 host, port = uri, None
390 else:
389 else:
391 host, port = uri[:cred_pos], uri[cred_pos + 1:]
390 host, port = uri[:cred_pos], uri[cred_pos + 1:]
392
391
393 return filter(None, [proto, host, port])
392 return filter(None, [proto, host, port])
394
393
395
394
396 def credentials_filter(uri):
395 def credentials_filter(uri):
397 """
396 """
398 Returns a url with removed credentials
397 Returns a url with removed credentials
399
398
400 :param uri:
399 :param uri:
401 """
400 """
402
401
403 uri = uri_filter(uri)
402 uri = uri_filter(uri)
404 #check if we have port
403 #check if we have port
405 if len(uri) > 2 and uri[2]:
404 if len(uri) > 2 and uri[2]:
406 uri[2] = ':' + uri[2]
405 uri[2] = ':' + uri[2]
407
406
408 return ''.join(uri)
407 return ''.join(uri)
409
408
410
409
411 def get_changeset_safe(repo, rev):
410 def get_changeset_safe(repo, rev):
412 """
411 """
413 Safe version of get_changeset if this changeset doesn't exists for a
412 Safe version of get_changeset if this changeset doesn't exists for a
414 repo it returns a Dummy one instead
413 repo it returns a Dummy one instead
415
414
416 :param repo:
415 :param repo:
417 :param rev:
416 :param rev:
418 """
417 """
419 from rhodecode.lib.vcs.backends.base import BaseRepository
418 from rhodecode.lib.vcs.backends.base import BaseRepository
420 from rhodecode.lib.vcs.exceptions import RepositoryError
419 from rhodecode.lib.vcs.exceptions import RepositoryError
421 from rhodecode.lib.vcs.backends.base import EmptyChangeset
420 from rhodecode.lib.vcs.backends.base import EmptyChangeset
422 if not isinstance(repo, BaseRepository):
421 if not isinstance(repo, BaseRepository):
423 raise Exception('You must pass an Repository '
422 raise Exception('You must pass an Repository '
424 'object as first argument got %s', type(repo))
423 'object as first argument got %s', type(repo))
425
424
426 try:
425 try:
427 cs = repo.get_changeset(rev)
426 cs = repo.get_changeset(rev)
428 except RepositoryError:
427 except RepositoryError:
429 cs = EmptyChangeset(requested_revision=rev)
428 cs = EmptyChangeset(requested_revision=rev)
430 return cs
429 return cs
431
430
432
431
433 def datetime_to_time(dt):
432 def datetime_to_time(dt):
434 if dt:
433 if dt:
435 return time.mktime(dt.timetuple())
434 return time.mktime(dt.timetuple())
436
435
437
436
438 def time_to_datetime(tm):
437 def time_to_datetime(tm):
439 if tm:
438 if tm:
440 if isinstance(tm, basestring):
439 if isinstance(tm, basestring):
441 try:
440 try:
442 tm = float(tm)
441 tm = float(tm)
443 except ValueError:
442 except ValueError:
444 return
443 return
445 return datetime.datetime.fromtimestamp(tm)
444 return datetime.datetime.fromtimestamp(tm)
446
445
447 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
446 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
448
447
449
448
450 def extract_mentioned_users(s):
449 def extract_mentioned_users(s):
451 """
450 """
452 Returns unique usernames from given string s that have @mention
451 Returns unique usernames from given string s that have @mention
453
452
454 :param s: string to get mentions
453 :param s: string to get mentions
455 """
454 """
456 usrs = set()
455 usrs = set()
457 for username in re.findall(MENTIONS_REGEX, s):
456 for username in re.findall(MENTIONS_REGEX, s):
458 usrs.add(username)
457 usrs.add(username)
459
458
460 return sorted(list(usrs), key=lambda k: k.lower())
459 return sorted(list(usrs), key=lambda k: k.lower())
461
460
462
461
463 class AttributeDict(dict):
462 class AttributeDict(dict):
464 def __getattr__(self, attr):
463 def __getattr__(self, attr):
465 return self.get(attr, None)
464 return self.get(attr, None)
466 __setattr__ = dict.__setitem__
465 __setattr__ = dict.__setitem__
467 __delattr__ = dict.__delitem__
466 __delattr__ = dict.__delitem__
@@ -1,660 +1,676 b''
1 """
1 """
2 Set of generic validators
2 Set of generic validators
3 """
3 """
4 import os
4 import os
5 import re
5 import re
6 import formencode
6 import formencode
7 import logging
7 import logging
8 from collections import defaultdict
8 from pylons.i18n.translation import _
9 from pylons.i18n.translation import _
9 from webhelpers.pylonslib.secure_form import authentication_token
10 from webhelpers.pylonslib.secure_form import authentication_token
10
11
11 from formencode.validators import (
12 from formencode.validators import (
12 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
13 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
13 NotEmpty
14 NotEmpty
14 )
15 )
16 from rhodecode.lib.compat import OrderedSet
15 from rhodecode.lib.utils import repo_name_slug
17 from rhodecode.lib.utils import repo_name_slug
16 from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User,\
18 from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User,\
17 ChangesetStatus
19 ChangesetStatus
18 from rhodecode.lib.exceptions import LdapImportError
20 from rhodecode.lib.exceptions import LdapImportError
19 from rhodecode.config.routing import ADMIN_PREFIX
21 from rhodecode.config.routing import ADMIN_PREFIX
20
22
21 # silence warnings and pylint
23 # silence warnings and pylint
22 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
24 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
23 NotEmpty
25 NotEmpty
24
26
25 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
26
28
27
29
28 class UniqueList(formencode.FancyValidator):
30 class UniqueList(formencode.FancyValidator):
29 """
31 """
30 Unique List !
32 Unique List !
31 """
33 """
32 messages = dict(
34 messages = dict(
33 empty=_('Value cannot be an empty list'),
35 empty=_('Value cannot be an empty list'),
34 missing_value=_('Value cannot be an empty list'),
36 missing_value=_('Value cannot be an empty list'),
35 )
37 )
36
38
37 def _to_python(self, value, state):
39 def _to_python(self, value, state):
38 if isinstance(value, list):
40 if isinstance(value, list):
39 return value
41 return value
40 elif isinstance(value, set):
42 elif isinstance(value, set):
41 return list(value)
43 return list(value)
42 elif isinstance(value, tuple):
44 elif isinstance(value, tuple):
43 return list(value)
45 return list(value)
44 elif value is None:
46 elif value is None:
45 return []
47 return []
46 else:
48 else:
47 return [value]
49 return [value]
48
50
49 def empty_value(self, value):
51 def empty_value(self, value):
50 return []
52 return []
51
53
52
54
53 class StateObj(object):
55 class StateObj(object):
54 """
56 """
55 this is needed to translate the messages using _() in validators
57 this is needed to translate the messages using _() in validators
56 """
58 """
57 _ = staticmethod(_)
59 _ = staticmethod(_)
58
60
59
61
60 def M(self, key, state=None, **kwargs):
62 def M(self, key, state=None, **kwargs):
61 """
63 """
62 returns string from self.message based on given key,
64 returns string from self.message based on given key,
63 passed kw params are used to substitute %(named)s params inside
65 passed kw params are used to substitute %(named)s params inside
64 translated strings
66 translated strings
65
67
66 :param msg:
68 :param msg:
67 :param state:
69 :param state:
68 """
70 """
69 if state is None:
71 if state is None:
70 state = StateObj()
72 state = StateObj()
71 else:
73 else:
72 state._ = staticmethod(_)
74 state._ = staticmethod(_)
73 #inject validator into state object
75 #inject validator into state object
74 return self.message(key, state, **kwargs)
76 return self.message(key, state, **kwargs)
75
77
76
78
77 def ValidUsername(edit=False, old_data={}):
79 def ValidUsername(edit=False, old_data={}):
78 class _validator(formencode.validators.FancyValidator):
80 class _validator(formencode.validators.FancyValidator):
79 messages = {
81 messages = {
80 'username_exists': _(u'Username "%(username)s" already exists'),
82 'username_exists': _(u'Username "%(username)s" already exists'),
81 'system_invalid_username':
83 'system_invalid_username':
82 _(u'Username "%(username)s" is forbidden'),
84 _(u'Username "%(username)s" is forbidden'),
83 'invalid_username':
85 'invalid_username':
84 _(u'Username may only contain alphanumeric characters '
86 _(u'Username may only contain alphanumeric characters '
85 'underscores, periods or dashes and must begin with '
87 'underscores, periods or dashes and must begin with '
86 'alphanumeric character')
88 'alphanumeric character')
87 }
89 }
88
90
89 def validate_python(self, value, state):
91 def validate_python(self, value, state):
90 if value in ['default', 'new_user']:
92 if value in ['default', 'new_user']:
91 msg = M(self, 'system_invalid_username', state, username=value)
93 msg = M(self, 'system_invalid_username', state, username=value)
92 raise formencode.Invalid(msg, value, state)
94 raise formencode.Invalid(msg, value, state)
93 #check if user is unique
95 #check if user is unique
94 old_un = None
96 old_un = None
95 if edit:
97 if edit:
96 old_un = User.get(old_data.get('user_id')).username
98 old_un = User.get(old_data.get('user_id')).username
97
99
98 if old_un != value or not edit:
100 if old_un != value or not edit:
99 if User.get_by_username(value, case_insensitive=True):
101 if User.get_by_username(value, case_insensitive=True):
100 msg = M(self, 'username_exists', state, username=value)
102 msg = M(self, 'username_exists', state, username=value)
101 raise formencode.Invalid(msg, value, state)
103 raise formencode.Invalid(msg, value, state)
102
104
103 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
105 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
104 msg = M(self, 'invalid_username', state)
106 msg = M(self, 'invalid_username', state)
105 raise formencode.Invalid(msg, value, state)
107 raise formencode.Invalid(msg, value, state)
106 return _validator
108 return _validator
107
109
108
110
109 def ValidRepoUser():
111 def ValidRepoUser():
110 class _validator(formencode.validators.FancyValidator):
112 class _validator(formencode.validators.FancyValidator):
111 messages = {
113 messages = {
112 'invalid_username': _(u'Username %(username)s is not valid')
114 'invalid_username': _(u'Username %(username)s is not valid')
113 }
115 }
114
116
115 def validate_python(self, value, state):
117 def validate_python(self, value, state):
116 try:
118 try:
117 User.query().filter(User.active == True)\
119 User.query().filter(User.active == True)\
118 .filter(User.username == value).one()
120 .filter(User.username == value).one()
119 except Exception:
121 except Exception:
120 msg = M(self, 'invalid_username', state, username=value)
122 msg = M(self, 'invalid_username', state, username=value)
121 raise formencode.Invalid(msg, value, state,
123 raise formencode.Invalid(msg, value, state,
122 error_dict=dict(username=msg)
124 error_dict=dict(username=msg)
123 )
125 )
124
126
125 return _validator
127 return _validator
126
128
127
129
128 def ValidUsersGroup(edit=False, old_data={}):
130 def ValidUsersGroup(edit=False, old_data={}):
129 class _validator(formencode.validators.FancyValidator):
131 class _validator(formencode.validators.FancyValidator):
130 messages = {
132 messages = {
131 'invalid_group': _(u'Invalid users group name'),
133 'invalid_group': _(u'Invalid users group name'),
132 'group_exist': _(u'Users group "%(usersgroup)s" already exists'),
134 'group_exist': _(u'Users group "%(usersgroup)s" already exists'),
133 'invalid_usersgroup_name':
135 'invalid_usersgroup_name':
134 _(u'users group name may only contain alphanumeric '
136 _(u'users group name may only contain alphanumeric '
135 'characters underscores, periods or dashes and must begin '
137 'characters underscores, periods or dashes and must begin '
136 'with alphanumeric character')
138 'with alphanumeric character')
137 }
139 }
138
140
139 def validate_python(self, value, state):
141 def validate_python(self, value, state):
140 if value in ['default']:
142 if value in ['default']:
141 msg = M(self, 'invalid_group', state)
143 msg = M(self, 'invalid_group', state)
142 raise formencode.Invalid(msg, value, state,
144 raise formencode.Invalid(msg, value, state,
143 error_dict=dict(users_group_name=msg)
145 error_dict=dict(users_group_name=msg)
144 )
146 )
145 #check if group is unique
147 #check if group is unique
146 old_ugname = None
148 old_ugname = None
147 if edit:
149 if edit:
148 old_id = old_data.get('users_group_id')
150 old_id = old_data.get('users_group_id')
149 old_ugname = UsersGroup.get(old_id).users_group_name
151 old_ugname = UsersGroup.get(old_id).users_group_name
150
152
151 if old_ugname != value or not edit:
153 if old_ugname != value or not edit:
152 is_existing_group = UsersGroup.get_by_group_name(value,
154 is_existing_group = UsersGroup.get_by_group_name(value,
153 case_insensitive=True)
155 case_insensitive=True)
154 if is_existing_group:
156 if is_existing_group:
155 msg = M(self, 'group_exist', state, usersgroup=value)
157 msg = M(self, 'group_exist', state, usersgroup=value)
156 raise formencode.Invalid(msg, value, state,
158 raise formencode.Invalid(msg, value, state,
157 error_dict=dict(users_group_name=msg)
159 error_dict=dict(users_group_name=msg)
158 )
160 )
159
161
160 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
162 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
161 msg = M(self, 'invalid_usersgroup_name', state)
163 msg = M(self, 'invalid_usersgroup_name', state)
162 raise formencode.Invalid(msg, value, state,
164 raise formencode.Invalid(msg, value, state,
163 error_dict=dict(users_group_name=msg)
165 error_dict=dict(users_group_name=msg)
164 )
166 )
165
167
166 return _validator
168 return _validator
167
169
168
170
169 def ValidReposGroup(edit=False, old_data={}):
171 def ValidReposGroup(edit=False, old_data={}):
170 class _validator(formencode.validators.FancyValidator):
172 class _validator(formencode.validators.FancyValidator):
171 messages = {
173 messages = {
172 'group_parent_id': _(u'Cannot assign this group as parent'),
174 'group_parent_id': _(u'Cannot assign this group as parent'),
173 'group_exists': _(u'Group "%(group_name)s" already exists'),
175 'group_exists': _(u'Group "%(group_name)s" already exists'),
174 'repo_exists':
176 'repo_exists':
175 _(u'Repository with name "%(group_name)s" already exists')
177 _(u'Repository with name "%(group_name)s" already exists')
176 }
178 }
177
179
178 def validate_python(self, value, state):
180 def validate_python(self, value, state):
179 # TODO WRITE VALIDATIONS
181 # TODO WRITE VALIDATIONS
180 group_name = value.get('group_name')
182 group_name = value.get('group_name')
181 group_parent_id = value.get('group_parent_id')
183 group_parent_id = value.get('group_parent_id')
182
184
183 # slugify repo group just in case :)
185 # slugify repo group just in case :)
184 slug = repo_name_slug(group_name)
186 slug = repo_name_slug(group_name)
185
187
186 # check for parent of self
188 # check for parent of self
187 parent_of_self = lambda: (
189 parent_of_self = lambda: (
188 old_data['group_id'] == int(group_parent_id)
190 old_data['group_id'] == int(group_parent_id)
189 if group_parent_id else False
191 if group_parent_id else False
190 )
192 )
191 if edit and parent_of_self():
193 if edit and parent_of_self():
192 msg = M(self, 'group_parent_id', state)
194 msg = M(self, 'group_parent_id', state)
193 raise formencode.Invalid(msg, value, state,
195 raise formencode.Invalid(msg, value, state,
194 error_dict=dict(group_parent_id=msg)
196 error_dict=dict(group_parent_id=msg)
195 )
197 )
196
198
197 old_gname = None
199 old_gname = None
198 if edit:
200 if edit:
199 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
201 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
200
202
201 if old_gname != group_name or not edit:
203 if old_gname != group_name or not edit:
202
204
203 # check group
205 # check group
204 gr = RepoGroup.query()\
206 gr = RepoGroup.query()\
205 .filter(RepoGroup.group_name == slug)\
207 .filter(RepoGroup.group_name == slug)\
206 .filter(RepoGroup.group_parent_id == group_parent_id)\
208 .filter(RepoGroup.group_parent_id == group_parent_id)\
207 .scalar()
209 .scalar()
208
210
209 if gr:
211 if gr:
210 msg = M(self, 'group_exists', state, group_name=slug)
212 msg = M(self, 'group_exists', state, group_name=slug)
211 raise formencode.Invalid(msg, value, state,
213 raise formencode.Invalid(msg, value, state,
212 error_dict=dict(group_name=msg)
214 error_dict=dict(group_name=msg)
213 )
215 )
214
216
215 # check for same repo
217 # check for same repo
216 repo = Repository.query()\
218 repo = Repository.query()\
217 .filter(Repository.repo_name == slug)\
219 .filter(Repository.repo_name == slug)\
218 .scalar()
220 .scalar()
219
221
220 if repo:
222 if repo:
221 msg = M(self, 'repo_exists', state, group_name=slug)
223 msg = M(self, 'repo_exists', state, group_name=slug)
222 raise formencode.Invalid(msg, value, state,
224 raise formencode.Invalid(msg, value, state,
223 error_dict=dict(group_name=msg)
225 error_dict=dict(group_name=msg)
224 )
226 )
225
227
226 return _validator
228 return _validator
227
229
228
230
229 def ValidPassword():
231 def ValidPassword():
230 class _validator(formencode.validators.FancyValidator):
232 class _validator(formencode.validators.FancyValidator):
231 messages = {
233 messages = {
232 'invalid_password':
234 'invalid_password':
233 _(u'Invalid characters (non-ascii) in password')
235 _(u'Invalid characters (non-ascii) in password')
234 }
236 }
235
237
236 def validate_python(self, value, state):
238 def validate_python(self, value, state):
237 try:
239 try:
238 (value or '').decode('ascii')
240 (value or '').decode('ascii')
239 except UnicodeError:
241 except UnicodeError:
240 msg = M(self, 'invalid_password', state)
242 msg = M(self, 'invalid_password', state)
241 raise formencode.Invalid(msg, value, state,)
243 raise formencode.Invalid(msg, value, state,)
242 return _validator
244 return _validator
243
245
244
246
245 def ValidPasswordsMatch():
247 def ValidPasswordsMatch():
246 class _validator(formencode.validators.FancyValidator):
248 class _validator(formencode.validators.FancyValidator):
247 messages = {
249 messages = {
248 'password_mismatch': _(u'Passwords do not match'),
250 'password_mismatch': _(u'Passwords do not match'),
249 }
251 }
250
252
251 def validate_python(self, value, state):
253 def validate_python(self, value, state):
252
254
253 pass_val = value.get('password') or value.get('new_password')
255 pass_val = value.get('password') or value.get('new_password')
254 if pass_val != value['password_confirmation']:
256 if pass_val != value['password_confirmation']:
255 msg = M(self, 'password_mismatch', state)
257 msg = M(self, 'password_mismatch', state)
256 raise formencode.Invalid(msg, value, state,
258 raise formencode.Invalid(msg, value, state,
257 error_dict=dict(password_confirmation=msg)
259 error_dict=dict(password_confirmation=msg)
258 )
260 )
259 return _validator
261 return _validator
260
262
261
263
262 def ValidAuth():
264 def ValidAuth():
263 class _validator(formencode.validators.FancyValidator):
265 class _validator(formencode.validators.FancyValidator):
264 messages = {
266 messages = {
265 'invalid_password': _(u'invalid password'),
267 'invalid_password': _(u'invalid password'),
266 'invalid_username': _(u'invalid user name'),
268 'invalid_username': _(u'invalid user name'),
267 'disabled_account': _(u'Your account is disabled')
269 'disabled_account': _(u'Your account is disabled')
268 }
270 }
269
271
270 def validate_python(self, value, state):
272 def validate_python(self, value, state):
271 from rhodecode.lib.auth import authenticate
273 from rhodecode.lib.auth import authenticate
272
274
273 password = value['password']
275 password = value['password']
274 username = value['username']
276 username = value['username']
275
277
276 if not authenticate(username, password):
278 if not authenticate(username, password):
277 user = User.get_by_username(username)
279 user = User.get_by_username(username)
278 if user and user.active is False:
280 if user and user.active is False:
279 log.warning('user %s is disabled' % username)
281 log.warning('user %s is disabled' % username)
280 msg = M(self, 'disabled_account', state)
282 msg = M(self, 'disabled_account', state)
281 raise formencode.Invalid(msg, value, state,
283 raise formencode.Invalid(msg, value, state,
282 error_dict=dict(username=msg)
284 error_dict=dict(username=msg)
283 )
285 )
284 else:
286 else:
285 log.warning('user %s failed to authenticate' % username)
287 log.warning('user %s failed to authenticate' % username)
286 msg = M(self, 'invalid_username', state)
288 msg = M(self, 'invalid_username', state)
287 msg2 = M(self, 'invalid_password', state)
289 msg2 = M(self, 'invalid_password', state)
288 raise formencode.Invalid(msg, value, state,
290 raise formencode.Invalid(msg, value, state,
289 error_dict=dict(username=msg, password=msg2)
291 error_dict=dict(username=msg, password=msg2)
290 )
292 )
291 return _validator
293 return _validator
292
294
293
295
294 def ValidAuthToken():
296 def ValidAuthToken():
295 class _validator(formencode.validators.FancyValidator):
297 class _validator(formencode.validators.FancyValidator):
296 messages = {
298 messages = {
297 'invalid_token': _(u'Token mismatch')
299 'invalid_token': _(u'Token mismatch')
298 }
300 }
299
301
300 def validate_python(self, value, state):
302 def validate_python(self, value, state):
301 if value != authentication_token():
303 if value != authentication_token():
302 msg = M(self, 'invalid_token', state)
304 msg = M(self, 'invalid_token', state)
303 raise formencode.Invalid(msg, value, state)
305 raise formencode.Invalid(msg, value, state)
304 return _validator
306 return _validator
305
307
306
308
307 def ValidRepoName(edit=False, old_data={}):
309 def ValidRepoName(edit=False, old_data={}):
308 class _validator(formencode.validators.FancyValidator):
310 class _validator(formencode.validators.FancyValidator):
309 messages = {
311 messages = {
310 'invalid_repo_name':
312 'invalid_repo_name':
311 _(u'Repository name %(repo)s is disallowed'),
313 _(u'Repository name %(repo)s is disallowed'),
312 'repository_exists':
314 'repository_exists':
313 _(u'Repository named %(repo)s already exists'),
315 _(u'Repository named %(repo)s already exists'),
314 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
316 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
315 'exists in group "%(group)s"'),
317 'exists in group "%(group)s"'),
316 'same_group_exists': _(u'Repositories group with name "%(repo)s" '
318 'same_group_exists': _(u'Repositories group with name "%(repo)s" '
317 'already exists')
319 'already exists')
318 }
320 }
319
321
320 def _to_python(self, value, state):
322 def _to_python(self, value, state):
321 repo_name = repo_name_slug(value.get('repo_name', ''))
323 repo_name = repo_name_slug(value.get('repo_name', ''))
322 repo_group = value.get('repo_group')
324 repo_group = value.get('repo_group')
323 if repo_group:
325 if repo_group:
324 gr = RepoGroup.get(repo_group)
326 gr = RepoGroup.get(repo_group)
325 group_path = gr.full_path
327 group_path = gr.full_path
326 group_name = gr.group_name
328 group_name = gr.group_name
327 # value needs to be aware of group name in order to check
329 # value needs to be aware of group name in order to check
328 # db key This is an actual just the name to store in the
330 # db key This is an actual just the name to store in the
329 # database
331 # database
330 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
332 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
331 else:
333 else:
332 group_name = group_path = ''
334 group_name = group_path = ''
333 repo_name_full = repo_name
335 repo_name_full = repo_name
334
336
335 value['repo_name'] = repo_name
337 value['repo_name'] = repo_name
336 value['repo_name_full'] = repo_name_full
338 value['repo_name_full'] = repo_name_full
337 value['group_path'] = group_path
339 value['group_path'] = group_path
338 value['group_name'] = group_name
340 value['group_name'] = group_name
339 return value
341 return value
340
342
341 def validate_python(self, value, state):
343 def validate_python(self, value, state):
342
344
343 repo_name = value.get('repo_name')
345 repo_name = value.get('repo_name')
344 repo_name_full = value.get('repo_name_full')
346 repo_name_full = value.get('repo_name_full')
345 group_path = value.get('group_path')
347 group_path = value.get('group_path')
346 group_name = value.get('group_name')
348 group_name = value.get('group_name')
347
349
348 if repo_name in [ADMIN_PREFIX, '']:
350 if repo_name in [ADMIN_PREFIX, '']:
349 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
351 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
350 raise formencode.Invalid(msg, value, state,
352 raise formencode.Invalid(msg, value, state,
351 error_dict=dict(repo_name=msg)
353 error_dict=dict(repo_name=msg)
352 )
354 )
353
355
354 rename = old_data.get('repo_name') != repo_name_full
356 rename = old_data.get('repo_name') != repo_name_full
355 create = not edit
357 create = not edit
356 if rename or create:
358 if rename or create:
357
359
358 if group_path != '':
360 if group_path != '':
359 if Repository.get_by_repo_name(repo_name_full):
361 if Repository.get_by_repo_name(repo_name_full):
360 msg = M(self, 'repository_in_group_exists', state,
362 msg = M(self, 'repository_in_group_exists', state,
361 repo=repo_name, group=group_name)
363 repo=repo_name, group=group_name)
362 raise formencode.Invalid(msg, value, state,
364 raise formencode.Invalid(msg, value, state,
363 error_dict=dict(repo_name=msg)
365 error_dict=dict(repo_name=msg)
364 )
366 )
365 elif RepoGroup.get_by_group_name(repo_name_full):
367 elif RepoGroup.get_by_group_name(repo_name_full):
366 msg = M(self, 'same_group_exists', state,
368 msg = M(self, 'same_group_exists', state,
367 repo=repo_name)
369 repo=repo_name)
368 raise formencode.Invalid(msg, value, state,
370 raise formencode.Invalid(msg, value, state,
369 error_dict=dict(repo_name=msg)
371 error_dict=dict(repo_name=msg)
370 )
372 )
371
373
372 elif Repository.get_by_repo_name(repo_name_full):
374 elif Repository.get_by_repo_name(repo_name_full):
373 msg = M(self, 'repository_exists', state,
375 msg = M(self, 'repository_exists', state,
374 repo=repo_name)
376 repo=repo_name)
375 raise formencode.Invalid(msg, value, state,
377 raise formencode.Invalid(msg, value, state,
376 error_dict=dict(repo_name=msg)
378 error_dict=dict(repo_name=msg)
377 )
379 )
378 return value
380 return value
379 return _validator
381 return _validator
380
382
381
383
382 def ValidForkName(*args, **kwargs):
384 def ValidForkName(*args, **kwargs):
383 return ValidRepoName(*args, **kwargs)
385 return ValidRepoName(*args, **kwargs)
384
386
385
387
386 def SlugifyName():
388 def SlugifyName():
387 class _validator(formencode.validators.FancyValidator):
389 class _validator(formencode.validators.FancyValidator):
388
390
389 def _to_python(self, value, state):
391 def _to_python(self, value, state):
390 return repo_name_slug(value)
392 return repo_name_slug(value)
391
393
392 def validate_python(self, value, state):
394 def validate_python(self, value, state):
393 pass
395 pass
394
396
395 return _validator
397 return _validator
396
398
397
399
398 def ValidCloneUri():
400 def ValidCloneUri():
399 from rhodecode.lib.utils import make_ui
401 from rhodecode.lib.utils import make_ui
400
402
401 def url_handler(repo_type, url, ui=None):
403 def url_handler(repo_type, url, ui=None):
402 if repo_type == 'hg':
404 if repo_type == 'hg':
403 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
405 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
404 from mercurial.httppeer import httppeer
406 from mercurial.httppeer import httppeer
405 if url.startswith('http'):
407 if url.startswith('http'):
406 ## initially check if it's at least the proper URL
408 ## initially check if it's at least the proper URL
407 ## or does it pass basic auth
409 ## or does it pass basic auth
408 MercurialRepository._check_url(url)
410 MercurialRepository._check_url(url)
409 httppeer(ui, url)._capabilities()
411 httppeer(ui, url)._capabilities()
410 elif url.startswith('svn+http'):
412 elif url.startswith('svn+http'):
411 from hgsubversion.svnrepo import svnremoterepo
413 from hgsubversion.svnrepo import svnremoterepo
412 svnremoterepo(ui, url).capabilities
414 svnremoterepo(ui, url).capabilities
413 elif url.startswith('git+http'):
415 elif url.startswith('git+http'):
414 raise NotImplementedError()
416 raise NotImplementedError()
415
417
416 elif repo_type == 'git':
418 elif repo_type == 'git':
417 from rhodecode.lib.vcs.backends.git.repository import GitRepository
419 from rhodecode.lib.vcs.backends.git.repository import GitRepository
418 if url.startswith('http'):
420 if url.startswith('http'):
419 ## initially check if it's at least the proper URL
421 ## initially check if it's at least the proper URL
420 ## or does it pass basic auth
422 ## or does it pass basic auth
421 GitRepository._check_url(url)
423 GitRepository._check_url(url)
422 elif url.startswith('svn+http'):
424 elif url.startswith('svn+http'):
423 raise NotImplementedError()
425 raise NotImplementedError()
424 elif url.startswith('hg+http'):
426 elif url.startswith('hg+http'):
425 raise NotImplementedError()
427 raise NotImplementedError()
426
428
427 class _validator(formencode.validators.FancyValidator):
429 class _validator(formencode.validators.FancyValidator):
428 messages = {
430 messages = {
429 'clone_uri': _(u'invalid clone url'),
431 'clone_uri': _(u'invalid clone url'),
430 'invalid_clone_uri': _(u'Invalid clone url, provide a '
432 'invalid_clone_uri': _(u'Invalid clone url, provide a '
431 'valid clone http(s)/svn+http(s) url')
433 'valid clone http(s)/svn+http(s) url')
432 }
434 }
433
435
434 def validate_python(self, value, state):
436 def validate_python(self, value, state):
435 repo_type = value.get('repo_type')
437 repo_type = value.get('repo_type')
436 url = value.get('clone_uri')
438 url = value.get('clone_uri')
437
439
438 if not url:
440 if not url:
439 pass
441 pass
440 else:
442 else:
441 try:
443 try:
442 url_handler(repo_type, url, make_ui('db', clear_session=False))
444 url_handler(repo_type, url, make_ui('db', clear_session=False))
443 except Exception:
445 except Exception:
444 log.exception('Url validation failed')
446 log.exception('Url validation failed')
445 msg = M(self, 'clone_uri')
447 msg = M(self, 'clone_uri')
446 raise formencode.Invalid(msg, value, state,
448 raise formencode.Invalid(msg, value, state,
447 error_dict=dict(clone_uri=msg)
449 error_dict=dict(clone_uri=msg)
448 )
450 )
449 return _validator
451 return _validator
450
452
451
453
452 def ValidForkType(old_data={}):
454 def ValidForkType(old_data={}):
453 class _validator(formencode.validators.FancyValidator):
455 class _validator(formencode.validators.FancyValidator):
454 messages = {
456 messages = {
455 'invalid_fork_type': _(u'Fork have to be the same type as parent')
457 'invalid_fork_type': _(u'Fork have to be the same type as parent')
456 }
458 }
457
459
458 def validate_python(self, value, state):
460 def validate_python(self, value, state):
459 if old_data['repo_type'] != value:
461 if old_data['repo_type'] != value:
460 msg = M(self, 'invalid_fork_type', state)
462 msg = M(self, 'invalid_fork_type', state)
461 raise formencode.Invalid(msg, value, state,
463 raise formencode.Invalid(msg, value, state,
462 error_dict=dict(repo_type=msg)
464 error_dict=dict(repo_type=msg)
463 )
465 )
464 return _validator
466 return _validator
465
467
466
468
467 def ValidPerms(type_='repo'):
469 def ValidPerms(type_='repo'):
468 if type_ == 'group':
470 if type_ == 'group':
469 EMPTY_PERM = 'group.none'
471 EMPTY_PERM = 'group.none'
470 elif type_ == 'repo':
472 elif type_ == 'repo':
471 EMPTY_PERM = 'repository.none'
473 EMPTY_PERM = 'repository.none'
472
474
473 class _validator(formencode.validators.FancyValidator):
475 class _validator(formencode.validators.FancyValidator):
474 messages = {
476 messages = {
475 'perm_new_member_name':
477 'perm_new_member_name':
476 _(u'This username or users group name is not valid')
478 _(u'This username or users group name is not valid')
477 }
479 }
478
480
479 def to_python(self, value, state):
481 def to_python(self, value, state):
480 perms_update = []
482 perms_update = OrderedSet()
481 perms_new = []
483 perms_new = OrderedSet()
482 # build a list of permission to update and new permission to create
484 # build a list of permission to update and new permission to create
483 for k, v in value.items():
485
484 # means new added member to permissions
486 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
487 new_perms_group = defaultdict(dict)
488 for k, v in value.copy().iteritems():
485 if k.startswith('perm_new_member'):
489 if k.startswith('perm_new_member'):
486 new_perm = value.get('perm_new_member', False)
490 del value[k]
487 new_member = value.get('perm_new_member_name', False)
491 _type, part = k.split('perm_new_member_')
488 new_type = value.get('perm_new_member_type')
492 args = part.split('_')
493 if len(args) == 1:
494 new_perms_group[args[0]]['perm'] = v
495 elif len(args) == 2:
496 _key, pos = args
497 new_perms_group[pos][_key] = v
489
498
490 if new_member and new_perm:
499 # fill new permissions in order of how they were added
491 if (new_member, new_perm, new_type) not in perms_new:
500 for k in sorted(map(int, new_perms_group.keys())):
492 perms_new.append((new_member, new_perm, new_type))
501 perm_dict = new_perms_group[str(k)]
493 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
502 new_member = perm_dict['name']
503 new_perm = perm_dict['perm']
504 new_type = perm_dict['type']
505 if new_member and new_perm and new_type:
506 perms_new.add((new_member, new_perm, new_type))
507
508 for k, v in value.iteritems():
509 if k.startswith('u_perm_') or k.startswith('g_perm_'):
494 member = k[7:]
510 member = k[7:]
495 t = {'u': 'user',
511 t = {'u': 'user',
496 'g': 'users_group'
512 'g': 'users_group'
497 }[k[0]]
513 }[k[0]]
498 if member == 'default':
514 if member == 'default':
499 if value.get('private'):
515 if value.get('private'):
500 # set none for default when updating to
516 # set none for default when updating to
501 # private repo
517 # private repo
502 v = EMPTY_PERM
518 v = EMPTY_PERM
503 perms_update.append((member, v, t))
519 perms_update.add((member, v, t))
504
520
505 value['perms_updates'] = perms_update
521 value['perms_updates'] = list(perms_update)
506 value['perms_new'] = perms_new
522 value['perms_new'] = list(perms_new)
507
523
508 # update permissions
524 # update permissions
509 for k, v, t in perms_new:
525 for k, v, t in perms_new:
510 try:
526 try:
511 if t is 'user':
527 if t is 'user':
512 self.user_db = User.query()\
528 self.user_db = User.query()\
513 .filter(User.active == True)\
529 .filter(User.active == True)\
514 .filter(User.username == k).one()
530 .filter(User.username == k).one()
515 if t is 'users_group':
531 if t is 'users_group':
516 self.user_db = UsersGroup.query()\
532 self.user_db = UsersGroup.query()\
517 .filter(UsersGroup.users_group_active == True)\
533 .filter(UsersGroup.users_group_active == True)\
518 .filter(UsersGroup.users_group_name == k).one()
534 .filter(UsersGroup.users_group_name == k).one()
519
535
520 except Exception:
536 except Exception:
521 log.exception('Updated permission failed')
537 log.exception('Updated permission failed')
522 msg = M(self, 'perm_new_member_type', state)
538 msg = M(self, 'perm_new_member_type', state)
523 raise formencode.Invalid(msg, value, state,
539 raise formencode.Invalid(msg, value, state,
524 error_dict=dict(perm_new_member_name=msg)
540 error_dict=dict(perm_new_member_name=msg)
525 )
541 )
526 return value
542 return value
527 return _validator
543 return _validator
528
544
529
545
530 def ValidSettings():
546 def ValidSettings():
531 class _validator(formencode.validators.FancyValidator):
547 class _validator(formencode.validators.FancyValidator):
532 def _to_python(self, value, state):
548 def _to_python(self, value, state):
533 # settings form can't edit user
549 # settings form can't edit user
534 if 'user' in value:
550 if 'user' in value:
535 del value['user']
551 del value['user']
536 return value
552 return value
537
553
538 def validate_python(self, value, state):
554 def validate_python(self, value, state):
539 pass
555 pass
540 return _validator
556 return _validator
541
557
542
558
543 def ValidPath():
559 def ValidPath():
544 class _validator(formencode.validators.FancyValidator):
560 class _validator(formencode.validators.FancyValidator):
545 messages = {
561 messages = {
546 'invalid_path': _(u'This is not a valid path')
562 'invalid_path': _(u'This is not a valid path')
547 }
563 }
548
564
549 def validate_python(self, value, state):
565 def validate_python(self, value, state):
550 if not os.path.isdir(value):
566 if not os.path.isdir(value):
551 msg = M(self, 'invalid_path', state)
567 msg = M(self, 'invalid_path', state)
552 raise formencode.Invalid(msg, value, state,
568 raise formencode.Invalid(msg, value, state,
553 error_dict=dict(paths_root_path=msg)
569 error_dict=dict(paths_root_path=msg)
554 )
570 )
555 return _validator
571 return _validator
556
572
557
573
558 def UniqSystemEmail(old_data={}):
574 def UniqSystemEmail(old_data={}):
559 class _validator(formencode.validators.FancyValidator):
575 class _validator(formencode.validators.FancyValidator):
560 messages = {
576 messages = {
561 'email_taken': _(u'This e-mail address is already taken')
577 'email_taken': _(u'This e-mail address is already taken')
562 }
578 }
563
579
564 def _to_python(self, value, state):
580 def _to_python(self, value, state):
565 return value.lower()
581 return value.lower()
566
582
567 def validate_python(self, value, state):
583 def validate_python(self, value, state):
568 if (old_data.get('email') or '').lower() != value:
584 if (old_data.get('email') or '').lower() != value:
569 user = User.get_by_email(value, case_insensitive=True)
585 user = User.get_by_email(value, case_insensitive=True)
570 if user:
586 if user:
571 msg = M(self, 'email_taken', state)
587 msg = M(self, 'email_taken', state)
572 raise formencode.Invalid(msg, value, state,
588 raise formencode.Invalid(msg, value, state,
573 error_dict=dict(email=msg)
589 error_dict=dict(email=msg)
574 )
590 )
575 return _validator
591 return _validator
576
592
577
593
578 def ValidSystemEmail():
594 def ValidSystemEmail():
579 class _validator(formencode.validators.FancyValidator):
595 class _validator(formencode.validators.FancyValidator):
580 messages = {
596 messages = {
581 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
597 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
582 }
598 }
583
599
584 def _to_python(self, value, state):
600 def _to_python(self, value, state):
585 return value.lower()
601 return value.lower()
586
602
587 def validate_python(self, value, state):
603 def validate_python(self, value, state):
588 user = User.get_by_email(value, case_insensitive=True)
604 user = User.get_by_email(value, case_insensitive=True)
589 if user is None:
605 if user is None:
590 msg = M(self, 'non_existing_email', state, email=value)
606 msg = M(self, 'non_existing_email', state, email=value)
591 raise formencode.Invalid(msg, value, state,
607 raise formencode.Invalid(msg, value, state,
592 error_dict=dict(email=msg)
608 error_dict=dict(email=msg)
593 )
609 )
594
610
595 return _validator
611 return _validator
596
612
597
613
598 def LdapLibValidator():
614 def LdapLibValidator():
599 class _validator(formencode.validators.FancyValidator):
615 class _validator(formencode.validators.FancyValidator):
600 messages = {
616 messages = {
601
617
602 }
618 }
603
619
604 def validate_python(self, value, state):
620 def validate_python(self, value, state):
605 try:
621 try:
606 import ldap
622 import ldap
607 ldap # pyflakes silence !
623 ldap # pyflakes silence !
608 except ImportError:
624 except ImportError:
609 raise LdapImportError()
625 raise LdapImportError()
610
626
611 return _validator
627 return _validator
612
628
613
629
614 def AttrLoginValidator():
630 def AttrLoginValidator():
615 class _validator(formencode.validators.FancyValidator):
631 class _validator(formencode.validators.FancyValidator):
616 messages = {
632 messages = {
617 'invalid_cn':
633 'invalid_cn':
618 _(u'The LDAP Login attribute of the CN must be specified - '
634 _(u'The LDAP Login attribute of the CN must be specified - '
619 'this is the name of the attribute that is equivalent '
635 'this is the name of the attribute that is equivalent '
620 'to "username"')
636 'to "username"')
621 }
637 }
622
638
623 def validate_python(self, value, state):
639 def validate_python(self, value, state):
624 if not value or not isinstance(value, (str, unicode)):
640 if not value or not isinstance(value, (str, unicode)):
625 msg = M(self, 'invalid_cn', state)
641 msg = M(self, 'invalid_cn', state)
626 raise formencode.Invalid(msg, value, state,
642 raise formencode.Invalid(msg, value, state,
627 error_dict=dict(ldap_attr_login=msg)
643 error_dict=dict(ldap_attr_login=msg)
628 )
644 )
629
645
630 return _validator
646 return _validator
631
647
632
648
633 def NotReviewedRevisions():
649 def NotReviewedRevisions():
634 class _validator(formencode.validators.FancyValidator):
650 class _validator(formencode.validators.FancyValidator):
635 messages = {
651 messages = {
636 'rev_already_reviewed':
652 'rev_already_reviewed':
637 _(u'Revisions %(revs)s are already part of pull request '
653 _(u'Revisions %(revs)s are already part of pull request '
638 'or have set status')
654 'or have set status')
639 }
655 }
640
656
641 def validate_python(self, value, state):
657 def validate_python(self, value, state):
642 # check revisions if they are not reviewed, or a part of another
658 # check revisions if they are not reviewed, or a part of another
643 # pull request
659 # pull request
644 statuses = ChangesetStatus.query()\
660 statuses = ChangesetStatus.query()\
645 .filter(ChangesetStatus.revision.in_(value)).all()
661 .filter(ChangesetStatus.revision.in_(value)).all()
646 errors = []
662 errors = []
647 for cs in statuses:
663 for cs in statuses:
648 if cs.pull_request_id:
664 if cs.pull_request_id:
649 errors.append(['pull_req', cs.revision[:12]])
665 errors.append(['pull_req', cs.revision[:12]])
650 elif cs.status:
666 elif cs.status:
651 errors.append(['status', cs.revision[:12]])
667 errors.append(['status', cs.revision[:12]])
652
668
653 if errors:
669 if errors:
654 revs = ','.join([x[1] for x in errors])
670 revs = ','.join([x[1] for x in errors])
655 msg = M(self, 'rev_already_reviewed', state, revs=revs)
671 msg = M(self, 'rev_already_reviewed', state, revs=revs)
656 raise formencode.Invalid(msg, value, state,
672 raise formencode.Invalid(msg, value, state,
657 error_dict=dict(revisions=revs)
673 error_dict=dict(revisions=revs)
658 )
674 )
659
675
660 return _validator
676 return _validator
@@ -1,1740 +1,1759 b''
1 /**
1 /**
2 RhodeCode JS Files
2 RhodeCode JS Files
3 **/
3 **/
4
4
5 if (typeof console == "undefined" || typeof console.log == "undefined"){
5 if (typeof console == "undefined" || typeof console.log == "undefined"){
6 console = { log: function() {} }
6 console = { log: function() {} }
7 }
7 }
8
8
9
9
10 var str_repeat = function(i, m) {
10 var str_repeat = function(i, m) {
11 for (var o = []; m > 0; o[--m] = i);
11 for (var o = []; m > 0; o[--m] = i);
12 return o.join('');
12 return o.join('');
13 };
13 };
14
14
15 /**
15 /**
16 * INJECT .format function into String
16 * INJECT .format function into String
17 * Usage: "My name is {0} {1}".format("Johny","Bravo")
17 * Usage: "My name is {0} {1}".format("Johny","Bravo")
18 * Return "My name is Johny Bravo"
18 * Return "My name is Johny Bravo"
19 * Inspired by https://gist.github.com/1049426
19 * Inspired by https://gist.github.com/1049426
20 */
20 */
21 String.prototype.format = function() {
21 String.prototype.format = function() {
22
22
23 function format() {
23 function format() {
24 var str = this;
24 var str = this;
25 var len = arguments.length+1;
25 var len = arguments.length+1;
26 var safe = undefined;
26 var safe = undefined;
27 var arg = undefined;
27 var arg = undefined;
28
28
29 // For each {0} {1} {n...} replace with the argument in that position. If
29 // For each {0} {1} {n...} replace with the argument in that position. If
30 // the argument is an object or an array it will be stringified to JSON.
30 // the argument is an object or an array it will be stringified to JSON.
31 for (var i=0; i < len; arg = arguments[i++]) {
31 for (var i=0; i < len; arg = arguments[i++]) {
32 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
32 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
33 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
33 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
34 }
34 }
35 return str;
35 return str;
36 }
36 }
37
37
38 // Save a reference of what may already exist under the property native.
38 // Save a reference of what may already exist under the property native.
39 // Allows for doing something like: if("".format.native) { /* use native */ }
39 // Allows for doing something like: if("".format.native) { /* use native */ }
40 format.native = String.prototype.format;
40 format.native = String.prototype.format;
41
41
42 // Replace the prototype property
42 // Replace the prototype property
43 return format;
43 return format;
44
44
45 }();
45 }();
46
46
47 String.prototype.strip = function(char) {
47 String.prototype.strip = function(char) {
48 if(char === undefined){
48 if(char === undefined){
49 char = '\\s';
49 char = '\\s';
50 }
50 }
51 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
51 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
52 }
52 }
53 String.prototype.lstrip = function(char) {
53 String.prototype.lstrip = function(char) {
54 if(char === undefined){
54 if(char === undefined){
55 char = '\\s';
55 char = '\\s';
56 }
56 }
57 return this.replace(new RegExp('^'+char+'+'),'');
57 return this.replace(new RegExp('^'+char+'+'),'');
58 }
58 }
59 String.prototype.rstrip = function(char) {
59 String.prototype.rstrip = function(char) {
60 if(char === undefined){
60 if(char === undefined){
61 char = '\\s';
61 char = '\\s';
62 }
62 }
63 return this.replace(new RegExp(''+char+'+$'),'');
63 return this.replace(new RegExp(''+char+'+$'),'');
64 }
64 }
65
65
66
66
67 if(!Array.prototype.indexOf) {
67 if(!Array.prototype.indexOf) {
68 Array.prototype.indexOf = function(needle) {
68 Array.prototype.indexOf = function(needle) {
69 for(var i = 0; i < this.length; i++) {
69 for(var i = 0; i < this.length; i++) {
70 if(this[i] === needle) {
70 if(this[i] === needle) {
71 return i;
71 return i;
72 }
72 }
73 }
73 }
74 return -1;
74 return -1;
75 };
75 };
76 }
76 }
77
77
78 // IE(CRAP) doesn't support previousElementSibling
78 // IE(CRAP) doesn't support previousElementSibling
79 var prevElementSibling = function( el ) {
79 var prevElementSibling = function( el ) {
80 if( el.previousElementSibling ) {
80 if( el.previousElementSibling ) {
81 return el.previousElementSibling;
81 return el.previousElementSibling;
82 } else {
82 } else {
83 while( el = el.previousSibling ) {
83 while( el = el.previousSibling ) {
84 if( el.nodeType === 1 ) return el;
84 if( el.nodeType === 1 ) return el;
85 }
85 }
86 }
86 }
87 }
87 }
88
88
89
89
90
90
91
91
92 /**
92 /**
93 * SmartColorGenerator
93 * SmartColorGenerator
94 *
94 *
95 *usage::
95 *usage::
96 * var CG = new ColorGenerator();
96 * var CG = new ColorGenerator();
97 * var col = CG.getColor(key); //returns array of RGB
97 * var col = CG.getColor(key); //returns array of RGB
98 * 'rgb({0})'.format(col.join(',')
98 * 'rgb({0})'.format(col.join(',')
99 *
99 *
100 * @returns {ColorGenerator}
100 * @returns {ColorGenerator}
101 */
101 */
102 var ColorGenerator = function(){
102 var ColorGenerator = function(){
103 this.GOLDEN_RATIO = 0.618033988749895;
103 this.GOLDEN_RATIO = 0.618033988749895;
104 this.CURRENT_RATIO = 0.22717784590367374 // this can be random
104 this.CURRENT_RATIO = 0.22717784590367374 // this can be random
105 this.HSV_1 = 0.75;//saturation
105 this.HSV_1 = 0.75;//saturation
106 this.HSV_2 = 0.95;
106 this.HSV_2 = 0.95;
107 this.color;
107 this.color;
108 this.cacheColorMap = {};
108 this.cacheColorMap = {};
109 };
109 };
110
110
111 ColorGenerator.prototype = {
111 ColorGenerator.prototype = {
112 getColor:function(key){
112 getColor:function(key){
113 if(this.cacheColorMap[key] !== undefined){
113 if(this.cacheColorMap[key] !== undefined){
114 return this.cacheColorMap[key];
114 return this.cacheColorMap[key];
115 }
115 }
116 else{
116 else{
117 this.cacheColorMap[key] = this.generateColor();
117 this.cacheColorMap[key] = this.generateColor();
118 return this.cacheColorMap[key];
118 return this.cacheColorMap[key];
119 }
119 }
120 },
120 },
121 _hsvToRgb:function(h,s,v){
121 _hsvToRgb:function(h,s,v){
122 if (s == 0.0)
122 if (s == 0.0)
123 return [v, v, v];
123 return [v, v, v];
124 i = parseInt(h * 6.0)
124 i = parseInt(h * 6.0)
125 f = (h * 6.0) - i
125 f = (h * 6.0) - i
126 p = v * (1.0 - s)
126 p = v * (1.0 - s)
127 q = v * (1.0 - s * f)
127 q = v * (1.0 - s * f)
128 t = v * (1.0 - s * (1.0 - f))
128 t = v * (1.0 - s * (1.0 - f))
129 i = i % 6
129 i = i % 6
130 if (i == 0)
130 if (i == 0)
131 return [v, t, p]
131 return [v, t, p]
132 if (i == 1)
132 if (i == 1)
133 return [q, v, p]
133 return [q, v, p]
134 if (i == 2)
134 if (i == 2)
135 return [p, v, t]
135 return [p, v, t]
136 if (i == 3)
136 if (i == 3)
137 return [p, q, v]
137 return [p, q, v]
138 if (i == 4)
138 if (i == 4)
139 return [t, p, v]
139 return [t, p, v]
140 if (i == 5)
140 if (i == 5)
141 return [v, p, q]
141 return [v, p, q]
142 },
142 },
143 generateColor:function(){
143 generateColor:function(){
144 this.CURRENT_RATIO = this.CURRENT_RATIO+this.GOLDEN_RATIO;
144 this.CURRENT_RATIO = this.CURRENT_RATIO+this.GOLDEN_RATIO;
145 this.CURRENT_RATIO = this.CURRENT_RATIO %= 1;
145 this.CURRENT_RATIO = this.CURRENT_RATIO %= 1;
146 HSV_tuple = [this.CURRENT_RATIO, this.HSV_1, this.HSV_2]
146 HSV_tuple = [this.CURRENT_RATIO, this.HSV_1, this.HSV_2]
147 RGB_tuple = this._hsvToRgb(HSV_tuple[0],HSV_tuple[1],HSV_tuple[2]);
147 RGB_tuple = this._hsvToRgb(HSV_tuple[0],HSV_tuple[1],HSV_tuple[2]);
148 function toRgb(v){
148 function toRgb(v){
149 return ""+parseInt(v*256)
149 return ""+parseInt(v*256)
150 }
150 }
151 return [toRgb(RGB_tuple[0]),toRgb(RGB_tuple[1]),toRgb(RGB_tuple[2])];
151 return [toRgb(RGB_tuple[0]),toRgb(RGB_tuple[1]),toRgb(RGB_tuple[2])];
152
152
153 }
153 }
154 }
154 }
155
155
156
156
157
157
158
158
159
159
160 /**
160 /**
161 * GLOBAL YUI Shortcuts
161 * GLOBAL YUI Shortcuts
162 */
162 */
163 var YUC = YAHOO.util.Connect;
163 var YUC = YAHOO.util.Connect;
164 var YUD = YAHOO.util.Dom;
164 var YUD = YAHOO.util.Dom;
165 var YUE = YAHOO.util.Event;
165 var YUE = YAHOO.util.Event;
166 var YUQ = YAHOO.util.Selector.query;
166 var YUQ = YAHOO.util.Selector.query;
167
167
168 // defines if push state is enabled for this browser ?
168 // defines if push state is enabled for this browser ?
169 var push_state_enabled = Boolean(
169 var push_state_enabled = Boolean(
170 window.history && window.history.pushState && window.history.replaceState
170 window.history && window.history.pushState && window.history.replaceState
171 && !( /* disable for versions of iOS before version 4.3 (8F190) */
171 && !( /* disable for versions of iOS before version 4.3 (8F190) */
172 (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent)
172 (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent)
173 /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
173 /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
174 || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
174 || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
175 )
175 )
176 );
176 );
177
177
178 var _run_callbacks = function(callbacks){
178 var _run_callbacks = function(callbacks){
179 if (callbacks !== undefined){
179 if (callbacks !== undefined){
180 var _l = callbacks.length;
180 var _l = callbacks.length;
181 for (var i=0;i<_l;i++){
181 for (var i=0;i<_l;i++){
182 var func = callbacks[i];
182 var func = callbacks[i];
183 if(typeof(func)=='function'){
183 if(typeof(func)=='function'){
184 try{
184 try{
185 func();
185 func();
186 }catch (err){};
186 }catch (err){};
187 }
187 }
188 }
188 }
189 }
189 }
190 }
190 }
191
191
192 /**
192 /**
193 * Partial Ajax Implementation
193 * Partial Ajax Implementation
194 *
194 *
195 * @param url: defines url to make partial request
195 * @param url: defines url to make partial request
196 * @param container: defines id of container to input partial result
196 * @param container: defines id of container to input partial result
197 * @param s_call: success callback function that takes o as arg
197 * @param s_call: success callback function that takes o as arg
198 * o.tId
198 * o.tId
199 * o.status
199 * o.status
200 * o.statusText
200 * o.statusText
201 * o.getResponseHeader[ ]
201 * o.getResponseHeader[ ]
202 * o.getAllResponseHeaders
202 * o.getAllResponseHeaders
203 * o.responseText
203 * o.responseText
204 * o.responseXML
204 * o.responseXML
205 * o.argument
205 * o.argument
206 * @param f_call: failure callback
206 * @param f_call: failure callback
207 * @param args arguments
207 * @param args arguments
208 */
208 */
209 function ypjax(url,container,s_call,f_call,args){
209 function ypjax(url,container,s_call,f_call,args){
210 var method='GET';
210 var method='GET';
211 if(args===undefined){
211 if(args===undefined){
212 args=null;
212 args=null;
213 }
213 }
214
214
215 // Set special header for partial ajax == HTTP_X_PARTIAL_XHR
215 // Set special header for partial ajax == HTTP_X_PARTIAL_XHR
216 YUC.initHeader('X-PARTIAL-XHR',true);
216 YUC.initHeader('X-PARTIAL-XHR',true);
217
217
218 // wrapper of passed callback
218 // wrapper of passed callback
219 var s_wrapper = (function(o){
219 var s_wrapper = (function(o){
220 return function(o){
220 return function(o){
221 YUD.get(container).innerHTML=o.responseText;
221 YUD.get(container).innerHTML=o.responseText;
222 YUD.setStyle(container,'opacity','1.0');
222 YUD.setStyle(container,'opacity','1.0');
223 //execute the given original callback
223 //execute the given original callback
224 if (s_call !== undefined){
224 if (s_call !== undefined){
225 s_call(o);
225 s_call(o);
226 }
226 }
227 }
227 }
228 })()
228 })()
229 YUD.setStyle(container,'opacity','0.3');
229 YUD.setStyle(container,'opacity','0.3');
230 YUC.asyncRequest(method,url,{
230 YUC.asyncRequest(method,url,{
231 success:s_wrapper,
231 success:s_wrapper,
232 failure:function(o){
232 failure:function(o){
233 console.log(o);
233 console.log(o);
234 YUD.get(container).innerHTML='<span class="error_red">ERROR: {0}</span>'.format(o.status);
234 YUD.get(container).innerHTML='<span class="error_red">ERROR: {0}</span>'.format(o.status);
235 YUD.setStyle(container,'opacity','1.0');
235 YUD.setStyle(container,'opacity','1.0');
236 },
236 },
237 cache:false
237 cache:false
238 },args);
238 },args);
239
239
240 };
240 };
241
241
242 var ajaxPOST = function(url,postData,success) {
242 var ajaxPOST = function(url,postData,success) {
243 // Set special header for ajax == HTTP_X_PARTIAL_XHR
243 // Set special header for ajax == HTTP_X_PARTIAL_XHR
244 YUC.initHeader('X-PARTIAL-XHR',true);
244 YUC.initHeader('X-PARTIAL-XHR',true);
245
245
246 var toQueryString = function(o) {
246 var toQueryString = function(o) {
247 if(typeof o !== 'object') {
247 if(typeof o !== 'object') {
248 return false;
248 return false;
249 }
249 }
250 var _p, _qs = [];
250 var _p, _qs = [];
251 for(_p in o) {
251 for(_p in o) {
252 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
252 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
253 }
253 }
254 return _qs.join('&');
254 return _qs.join('&');
255 };
255 };
256
256
257 var sUrl = url;
257 var sUrl = url;
258 var callback = {
258 var callback = {
259 success: success,
259 success: success,
260 failure: function (o) {
260 failure: function (o) {
261 alert("error");
261 alert("error");
262 },
262 },
263 };
263 };
264 var postData = toQueryString(postData);
264 var postData = toQueryString(postData);
265 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
265 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
266 return request;
266 return request;
267 };
267 };
268
268
269
269
270 /**
270 /**
271 * tooltip activate
271 * tooltip activate
272 */
272 */
273 var tooltip_activate = function(){
273 var tooltip_activate = function(){
274 function toolTipsId(){
274 function toolTipsId(){
275 var ids = [];
275 var ids = [];
276 var tts = YUQ('.tooltip');
276 var tts = YUQ('.tooltip');
277 for (var i = 0; i < tts.length; i++) {
277 for (var i = 0; i < tts.length; i++) {
278 // if element doesn't not have and id
278 // if element doesn't not have and id
279 // autogenerate one for tooltip
279 // autogenerate one for tooltip
280 if (!tts[i].id){
280 if (!tts[i].id){
281 tts[i].id='tt'+((i*100)+tts.length);
281 tts[i].id='tt'+((i*100)+tts.length);
282 }
282 }
283 ids.push(tts[i].id);
283 ids.push(tts[i].id);
284 }
284 }
285 return ids
285 return ids
286 };
286 };
287 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
287 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
288 context: [[toolTipsId()],"tl","bl",null,[0,5]],
288 context: [[toolTipsId()],"tl","bl",null,[0,5]],
289 monitorresize:false,
289 monitorresize:false,
290 xyoffset :[0,0],
290 xyoffset :[0,0],
291 autodismissdelay:300000,
291 autodismissdelay:300000,
292 hidedelay:5,
292 hidedelay:5,
293 showdelay:20,
293 showdelay:20,
294 });
294 });
295 };
295 };
296
296
297 /**
297 /**
298 * show more
298 * show more
299 */
299 */
300 var show_more_event = function(){
300 var show_more_event = function(){
301 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
301 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
302 var el = e.target;
302 var el = e.target;
303 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
303 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
304 YUD.setStyle(el.parentNode,'display','none');
304 YUD.setStyle(el.parentNode,'display','none');
305 });
305 });
306 };
306 };
307
307
308
308
309 /**
309 /**
310 * Quick filter widget
310 * Quick filter widget
311 *
311 *
312 * @param target: filter input target
312 * @param target: filter input target
313 * @param nodes: list of nodes in html we want to filter.
313 * @param nodes: list of nodes in html we want to filter.
314 * @param display_element function that takes current node from nodes and
314 * @param display_element function that takes current node from nodes and
315 * does hide or show based on the node
315 * does hide or show based on the node
316 *
316 *
317 */
317 */
318 var q_filter = function(target,nodes,display_element){
318 var q_filter = function(target,nodes,display_element){
319
319
320 var nodes = nodes;
320 var nodes = nodes;
321 var q_filter_field = YUD.get(target);
321 var q_filter_field = YUD.get(target);
322 var F = YAHOO.namespace(target);
322 var F = YAHOO.namespace(target);
323
323
324 YUE.on(q_filter_field,'click',function(){
324 YUE.on(q_filter_field,'click',function(){
325 q_filter_field.value = '';
325 q_filter_field.value = '';
326 });
326 });
327
327
328 YUE.on(q_filter_field,'keyup',function(e){
328 YUE.on(q_filter_field,'keyup',function(e){
329 clearTimeout(F.filterTimeout);
329 clearTimeout(F.filterTimeout);
330 F.filterTimeout = setTimeout(F.updateFilter,600);
330 F.filterTimeout = setTimeout(F.updateFilter,600);
331 });
331 });
332
332
333 F.filterTimeout = null;
333 F.filterTimeout = null;
334
334
335 var show_node = function(node){
335 var show_node = function(node){
336 YUD.setStyle(node,'display','')
336 YUD.setStyle(node,'display','')
337 }
337 }
338 var hide_node = function(node){
338 var hide_node = function(node){
339 YUD.setStyle(node,'display','none');
339 YUD.setStyle(node,'display','none');
340 }
340 }
341
341
342 F.updateFilter = function() {
342 F.updateFilter = function() {
343 // Reset timeout
343 // Reset timeout
344 F.filterTimeout = null;
344 F.filterTimeout = null;
345
345
346 var obsolete = [];
346 var obsolete = [];
347
347
348 var req = q_filter_field.value.toLowerCase();
348 var req = q_filter_field.value.toLowerCase();
349
349
350 var l = nodes.length;
350 var l = nodes.length;
351 var i;
351 var i;
352 var showing = 0;
352 var showing = 0;
353
353
354 for (i=0;i<l;i++ ){
354 for (i=0;i<l;i++ ){
355 var n = nodes[i];
355 var n = nodes[i];
356 var target_element = display_element(n)
356 var target_element = display_element(n)
357 if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
357 if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
358 hide_node(target_element);
358 hide_node(target_element);
359 }
359 }
360 else{
360 else{
361 show_node(target_element);
361 show_node(target_element);
362 showing+=1;
362 showing+=1;
363 }
363 }
364 }
364 }
365
365
366 // if repo_count is set update the number
366 // if repo_count is set update the number
367 var cnt = YUD.get('repo_count');
367 var cnt = YUD.get('repo_count');
368 if(cnt){
368 if(cnt){
369 YUD.get('repo_count').innerHTML = showing;
369 YUD.get('repo_count').innerHTML = showing;
370 }
370 }
371
371
372 }
372 }
373 };
373 };
374
374
375 var tableTr = function(cls,body){
375 var tableTr = function(cls,body){
376 var tr = document.createElement('tr');
376 var tr = document.createElement('tr');
377 YUD.addClass(tr, cls);
377 YUD.addClass(tr, cls);
378
378
379
379
380 var cont = new YAHOO.util.Element(body);
380 var cont = new YAHOO.util.Element(body);
381 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
381 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
382 tr.id = 'comment-tr-{0}'.format(comment_id);
382 tr.id = 'comment-tr-{0}'.format(comment_id);
383 tr.innerHTML = '<td class="lineno-inline new-inline"></td>'+
383 tr.innerHTML = '<td class="lineno-inline new-inline"></td>'+
384 '<td class="lineno-inline old-inline"></td>'+
384 '<td class="lineno-inline old-inline"></td>'+
385 '<td>{0}</td>'.format(body);
385 '<td>{0}</td>'.format(body);
386 return tr;
386 return tr;
387 };
387 };
388
388
389 /** comments **/
389 /** comments **/
390 var removeInlineForm = function(form) {
390 var removeInlineForm = function(form) {
391 form.parentNode.removeChild(form);
391 form.parentNode.removeChild(form);
392 };
392 };
393
393
394 var createInlineForm = function(parent_tr, f_path, line) {
394 var createInlineForm = function(parent_tr, f_path, line) {
395 var tmpl = YUD.get('comment-inline-form-template').innerHTML;
395 var tmpl = YUD.get('comment-inline-form-template').innerHTML;
396 tmpl = tmpl.format(f_path, line);
396 tmpl = tmpl.format(f_path, line);
397 var form = tableTr('comment-form-inline',tmpl)
397 var form = tableTr('comment-form-inline',tmpl)
398
398
399 // create event for hide button
399 // create event for hide button
400 form = new YAHOO.util.Element(form);
400 form = new YAHOO.util.Element(form);
401 var form_hide_button = new YAHOO.util.Element(YUD.getElementsByClassName('hide-inline-form',null,form)[0]);
401 var form_hide_button = new YAHOO.util.Element(YUD.getElementsByClassName('hide-inline-form',null,form)[0]);
402 form_hide_button.on('click', function(e) {
402 form_hide_button.on('click', function(e) {
403 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
403 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
404 if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
404 if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
405 YUD.setStyle(newtr.nextElementSibling,'display','');
405 YUD.setStyle(newtr.nextElementSibling,'display','');
406 }
406 }
407 removeInlineForm(newtr);
407 removeInlineForm(newtr);
408 YUD.removeClass(parent_tr, 'form-open');
408 YUD.removeClass(parent_tr, 'form-open');
409
409
410 });
410 });
411
411
412 return form
412 return form
413 };
413 };
414
414
415 /**
415 /**
416 * Inject inline comment for on given TR this tr should be always an .line
416 * Inject inline comment for on given TR this tr should be always an .line
417 * tr containing the line. Code will detect comment, and always put the comment
417 * tr containing the line. Code will detect comment, and always put the comment
418 * block at the very bottom
418 * block at the very bottom
419 */
419 */
420 var injectInlineForm = function(tr){
420 var injectInlineForm = function(tr){
421 if(!YUD.hasClass(tr, 'line')){
421 if(!YUD.hasClass(tr, 'line')){
422 return
422 return
423 }
423 }
424 var submit_url = AJAX_COMMENT_URL;
424 var submit_url = AJAX_COMMENT_URL;
425 var _td = YUD.getElementsByClassName('code',null,tr)[0];
425 var _td = YUD.getElementsByClassName('code',null,tr)[0];
426 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(_td,'no-comment')){
426 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(_td,'no-comment')){
427 return
427 return
428 }
428 }
429 YUD.addClass(tr,'form-open');
429 YUD.addClass(tr,'form-open');
430 var node = YUD.getElementsByClassName('full_f_path',null,tr.parentNode.parentNode.parentNode)[0];
430 var node = YUD.getElementsByClassName('full_f_path',null,tr.parentNode.parentNode.parentNode)[0];
431 var f_path = YUD.getAttribute(node,'path');
431 var f_path = YUD.getAttribute(node,'path');
432 var lineno = getLineNo(tr);
432 var lineno = getLineNo(tr);
433 var form = createInlineForm(tr, f_path, lineno, submit_url);
433 var form = createInlineForm(tr, f_path, lineno, submit_url);
434
434
435 var parent = tr;
435 var parent = tr;
436 while (1){
436 while (1){
437 var n = parent.nextElementSibling;
437 var n = parent.nextElementSibling;
438 // next element are comments !
438 // next element are comments !
439 if(YUD.hasClass(n,'inline-comments')){
439 if(YUD.hasClass(n,'inline-comments')){
440 parent = n;
440 parent = n;
441 }
441 }
442 else{
442 else{
443 break;
443 break;
444 }
444 }
445 }
445 }
446 YUD.insertAfter(form,parent);
446 YUD.insertAfter(form,parent);
447
447
448 var f = YUD.get(form);
448 var f = YUD.get(form);
449
449
450 var overlay = YUD.getElementsByClassName('overlay',null,f)[0];
450 var overlay = YUD.getElementsByClassName('overlay',null,f)[0];
451 var _form = YUD.getElementsByClassName('inline-form',null,f)[0];
451 var _form = YUD.getElementsByClassName('inline-form',null,f)[0];
452
452
453 form.on('submit',function(e){
453 form.on('submit',function(e){
454 YUE.preventDefault(e);
454 YUE.preventDefault(e);
455
455
456 //ajax submit
456 //ajax submit
457 var text = YUD.get('text_'+lineno).value;
457 var text = YUD.get('text_'+lineno).value;
458 var postData = {
458 var postData = {
459 'text':text,
459 'text':text,
460 'f_path':f_path,
460 'f_path':f_path,
461 'line':lineno
461 'line':lineno
462 };
462 };
463
463
464 if(lineno === undefined){
464 if(lineno === undefined){
465 alert('missing line !');
465 alert('missing line !');
466 return
466 return
467 }
467 }
468 if(f_path === undefined){
468 if(f_path === undefined){
469 alert('missing file path !');
469 alert('missing file path !');
470 return
470 return
471 }
471 }
472
472
473 if(text == ""){
473 if(text == ""){
474 return
474 return
475 }
475 }
476
476
477 var success = function(o){
477 var success = function(o){
478 YUD.removeClass(tr, 'form-open');
478 YUD.removeClass(tr, 'form-open');
479 removeInlineForm(f);
479 removeInlineForm(f);
480 var json_data = JSON.parse(o.responseText);
480 var json_data = JSON.parse(o.responseText);
481 renderInlineComment(json_data);
481 renderInlineComment(json_data);
482 };
482 };
483
483
484 if (YUD.hasClass(overlay,'overlay')){
484 if (YUD.hasClass(overlay,'overlay')){
485 var w = _form.offsetWidth;
485 var w = _form.offsetWidth;
486 var h = _form.offsetHeight;
486 var h = _form.offsetHeight;
487 YUD.setStyle(overlay,'width',w+'px');
487 YUD.setStyle(overlay,'width',w+'px');
488 YUD.setStyle(overlay,'height',h+'px');
488 YUD.setStyle(overlay,'height',h+'px');
489 }
489 }
490 YUD.addClass(overlay, 'submitting');
490 YUD.addClass(overlay, 'submitting');
491
491
492 ajaxPOST(submit_url, postData, success);
492 ajaxPOST(submit_url, postData, success);
493 });
493 });
494
494
495 setTimeout(function(){
495 setTimeout(function(){
496 // callbacks
496 // callbacks
497 tooltip_activate();
497 tooltip_activate();
498 MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno,
498 MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno,
499 _USERS_AC_DATA, _GROUPS_AC_DATA);
499 _USERS_AC_DATA, _GROUPS_AC_DATA);
500 var _e = YUD.get('text_'+lineno);
500 var _e = YUD.get('text_'+lineno);
501 if(_e){
501 if(_e){
502 _e.focus();
502 _e.focus();
503 }
503 }
504 },10)
504 },10)
505 };
505 };
506
506
507 var deleteComment = function(comment_id){
507 var deleteComment = function(comment_id){
508 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
508 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
509 var postData = {'_method':'delete'};
509 var postData = {'_method':'delete'};
510 var success = function(o){
510 var success = function(o){
511 var n = YUD.get('comment-tr-'+comment_id);
511 var n = YUD.get('comment-tr-'+comment_id);
512 var root = prevElementSibling(prevElementSibling(n));
512 var root = prevElementSibling(prevElementSibling(n));
513 n.parentNode.removeChild(n);
513 n.parentNode.removeChild(n);
514
514
515 // scann nodes, and attach add button to last one
515 // scann nodes, and attach add button to last one
516 placeAddButton(root);
516 placeAddButton(root);
517 }
517 }
518 ajaxPOST(url,postData,success);
518 ajaxPOST(url,postData,success);
519 }
519 }
520
520
521 var updateReviewers = function(reviewers_ids){
521 var updateReviewers = function(reviewers_ids){
522 var url = AJAX_UPDATE_PULLREQUEST;
522 var url = AJAX_UPDATE_PULLREQUEST;
523 var postData = {'_method':'put',
523 var postData = {'_method':'put',
524 'reviewers_ids': reviewers_ids};
524 'reviewers_ids': reviewers_ids};
525 var success = function(o){
525 var success = function(o){
526 window.location.reload();
526 window.location.reload();
527 }
527 }
528 ajaxPOST(url,postData,success);
528 ajaxPOST(url,postData,success);
529 }
529 }
530
530
531 var createInlineAddButton = function(tr){
531 var createInlineAddButton = function(tr){
532
532
533 var label = TRANSLATION_MAP['add another comment'];
533 var label = TRANSLATION_MAP['add another comment'];
534
534
535 var html_el = document.createElement('div');
535 var html_el = document.createElement('div');
536 YUD.addClass(html_el, 'add-comment');
536 YUD.addClass(html_el, 'add-comment');
537 html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
537 html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
538
538
539 var add = new YAHOO.util.Element(html_el);
539 var add = new YAHOO.util.Element(html_el);
540 add.on('click', function(e) {
540 add.on('click', function(e) {
541 injectInlineForm(tr);
541 injectInlineForm(tr);
542 });
542 });
543 return add;
543 return add;
544 };
544 };
545
545
546 var getLineNo = function(tr) {
546 var getLineNo = function(tr) {
547 var line;
547 var line;
548 var o = tr.children[0].id.split('_');
548 var o = tr.children[0].id.split('_');
549 var n = tr.children[1].id.split('_');
549 var n = tr.children[1].id.split('_');
550
550
551 if (n.length >= 2) {
551 if (n.length >= 2) {
552 line = n[n.length-1];
552 line = n[n.length-1];
553 } else if (o.length >= 2) {
553 } else if (o.length >= 2) {
554 line = o[o.length-1];
554 line = o[o.length-1];
555 }
555 }
556
556
557 return line
557 return line
558 };
558 };
559
559
560 var placeAddButton = function(target_tr){
560 var placeAddButton = function(target_tr){
561 if(!target_tr){
561 if(!target_tr){
562 return
562 return
563 }
563 }
564 var last_node = target_tr;
564 var last_node = target_tr;
565 //scann
565 //scann
566 while (1){
566 while (1){
567 var n = last_node.nextElementSibling;
567 var n = last_node.nextElementSibling;
568 // next element are comments !
568 // next element are comments !
569 if(YUD.hasClass(n,'inline-comments')){
569 if(YUD.hasClass(n,'inline-comments')){
570 last_node = n;
570 last_node = n;
571 //also remove the comment button from previous
571 //also remove the comment button from previous
572 var comment_add_buttons = YUD.getElementsByClassName('add-comment',null,last_node);
572 var comment_add_buttons = YUD.getElementsByClassName('add-comment',null,last_node);
573 for(var i=0;i<comment_add_buttons.length;i++){
573 for(var i=0;i<comment_add_buttons.length;i++){
574 var b = comment_add_buttons[i];
574 var b = comment_add_buttons[i];
575 b.parentNode.removeChild(b);
575 b.parentNode.removeChild(b);
576 }
576 }
577 }
577 }
578 else{
578 else{
579 break;
579 break;
580 }
580 }
581 }
581 }
582
582
583 var add = createInlineAddButton(target_tr);
583 var add = createInlineAddButton(target_tr);
584 // get the comment div
584 // get the comment div
585 var comment_block = YUD.getElementsByClassName('comment',null,last_node)[0];
585 var comment_block = YUD.getElementsByClassName('comment',null,last_node)[0];
586 // attach add button
586 // attach add button
587 YUD.insertAfter(add,comment_block);
587 YUD.insertAfter(add,comment_block);
588 }
588 }
589
589
590 /**
590 /**
591 * Places the inline comment into the changeset block in proper line position
591 * Places the inline comment into the changeset block in proper line position
592 */
592 */
593 var placeInline = function(target_container,lineno,html){
593 var placeInline = function(target_container,lineno,html){
594 var lineid = "{0}_{1}".format(target_container,lineno);
594 var lineid = "{0}_{1}".format(target_container,lineno);
595 var target_line = YUD.get(lineid);
595 var target_line = YUD.get(lineid);
596 var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
596 var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
597
597
598 // check if there are comments already !
598 // check if there are comments already !
599 var parent = target_line.parentNode;
599 var parent = target_line.parentNode;
600 var root_parent = parent;
600 var root_parent = parent;
601 while (1){
601 while (1){
602 var n = parent.nextElementSibling;
602 var n = parent.nextElementSibling;
603 // next element are comments !
603 // next element are comments !
604 if(YUD.hasClass(n,'inline-comments')){
604 if(YUD.hasClass(n,'inline-comments')){
605 parent = n;
605 parent = n;
606 }
606 }
607 else{
607 else{
608 break;
608 break;
609 }
609 }
610 }
610 }
611 // put in the comment at the bottom
611 // put in the comment at the bottom
612 YUD.insertAfter(comment,parent);
612 YUD.insertAfter(comment,parent);
613
613
614 // scann nodes, and attach add button to last one
614 // scann nodes, and attach add button to last one
615 placeAddButton(root_parent);
615 placeAddButton(root_parent);
616
616
617 return target_line;
617 return target_line;
618 }
618 }
619
619
620 /**
620 /**
621 * make a single inline comment and place it inside
621 * make a single inline comment and place it inside
622 */
622 */
623 var renderInlineComment = function(json_data){
623 var renderInlineComment = function(json_data){
624 try{
624 try{
625 var html = json_data['rendered_text'];
625 var html = json_data['rendered_text'];
626 var lineno = json_data['line_no'];
626 var lineno = json_data['line_no'];
627 var target_id = json_data['target_id'];
627 var target_id = json_data['target_id'];
628 placeInline(target_id, lineno, html);
628 placeInline(target_id, lineno, html);
629
629
630 }catch(e){
630 }catch(e){
631 console.log(e);
631 console.log(e);
632 }
632 }
633 }
633 }
634
634
635 /**
635 /**
636 * Iterates over all the inlines, and places them inside proper blocks of data
636 * Iterates over all the inlines, and places them inside proper blocks of data
637 */
637 */
638 var renderInlineComments = function(file_comments){
638 var renderInlineComments = function(file_comments){
639 for (f in file_comments){
639 for (f in file_comments){
640 // holding all comments for a FILE
640 // holding all comments for a FILE
641 var box = file_comments[f];
641 var box = file_comments[f];
642
642
643 var target_id = YUD.getAttribute(box,'target_id');
643 var target_id = YUD.getAttribute(box,'target_id');
644 // actually comments with line numbers
644 // actually comments with line numbers
645 var comments = box.children;
645 var comments = box.children;
646 for(var i=0; i<comments.length; i++){
646 for(var i=0; i<comments.length; i++){
647 var data = {
647 var data = {
648 'rendered_text': comments[i].outerHTML,
648 'rendered_text': comments[i].outerHTML,
649 'line_no': YUD.getAttribute(comments[i],'line'),
649 'line_no': YUD.getAttribute(comments[i],'line'),
650 'target_id': target_id
650 'target_id': target_id
651 }
651 }
652 renderInlineComment(data);
652 renderInlineComment(data);
653 }
653 }
654 }
654 }
655 }
655 }
656
656
657 var removeReviewer = function(reviewer_id){
657 var removeReviewer = function(reviewer_id){
658 var el = YUD.get('reviewer_{0}'.format(reviewer_id));
658 var el = YUD.get('reviewer_{0}'.format(reviewer_id));
659 if (el.parentNode !== undefined){
659 if (el.parentNode !== undefined){
660 el.parentNode.removeChild(el);
660 el.parentNode.removeChild(el);
661 }
661 }
662 }
662 }
663
663
664 var fileBrowserListeners = function(current_url, node_list_url, url_base){
664 var fileBrowserListeners = function(current_url, node_list_url, url_base){
665
665
666 var current_url_branch = +"?branch=__BRANCH__";
666 var current_url_branch = +"?branch=__BRANCH__";
667 var url = url_base;
667 var url = url_base;
668 var node_url = node_list_url;
668 var node_url = node_list_url;
669
669
670 YUE.on('stay_at_branch','click',function(e){
670 YUE.on('stay_at_branch','click',function(e){
671 if(e.target.checked){
671 if(e.target.checked){
672 var uri = current_url_branch;
672 var uri = current_url_branch;
673 uri = uri.replace('__BRANCH__',e.target.value);
673 uri = uri.replace('__BRANCH__',e.target.value);
674 window.location = uri;
674 window.location = uri;
675 }
675 }
676 else{
676 else{
677 window.location = current_url;
677 window.location = current_url;
678 }
678 }
679 })
679 })
680
680
681 var n_filter = YUD.get('node_filter');
681 var n_filter = YUD.get('node_filter');
682 var F = YAHOO.namespace('node_filter');
682 var F = YAHOO.namespace('node_filter');
683
683
684 F.filterTimeout = null;
684 F.filterTimeout = null;
685 var nodes = null;
685 var nodes = null;
686
686
687 F.initFilter = function(){
687 F.initFilter = function(){
688 YUD.setStyle('node_filter_box_loading','display','');
688 YUD.setStyle('node_filter_box_loading','display','');
689 YUD.setStyle('search_activate_id','display','none');
689 YUD.setStyle('search_activate_id','display','none');
690 YUD.setStyle('add_node_id','display','none');
690 YUD.setStyle('add_node_id','display','none');
691 YUC.initHeader('X-PARTIAL-XHR',true);
691 YUC.initHeader('X-PARTIAL-XHR',true);
692 YUC.asyncRequest('GET',url,{
692 YUC.asyncRequest('GET',url,{
693 success:function(o){
693 success:function(o){
694 nodes = JSON.parse(o.responseText).nodes;
694 nodes = JSON.parse(o.responseText).nodes;
695 YUD.setStyle('node_filter_box_loading','display','none');
695 YUD.setStyle('node_filter_box_loading','display','none');
696 YUD.setStyle('node_filter_box','display','');
696 YUD.setStyle('node_filter_box','display','');
697 n_filter.focus();
697 n_filter.focus();
698 if(YUD.hasClass(n_filter,'init')){
698 if(YUD.hasClass(n_filter,'init')){
699 n_filter.value = '';
699 n_filter.value = '';
700 YUD.removeClass(n_filter,'init');
700 YUD.removeClass(n_filter,'init');
701 }
701 }
702 },
702 },
703 failure:function(o){
703 failure:function(o){
704 console.log('failed to load');
704 console.log('failed to load');
705 }
705 }
706 },null);
706 },null);
707 }
707 }
708
708
709 F.updateFilter = function(e) {
709 F.updateFilter = function(e) {
710
710
711 return function(){
711 return function(){
712 // Reset timeout
712 // Reset timeout
713 F.filterTimeout = null;
713 F.filterTimeout = null;
714 var query = e.target.value.toLowerCase();
714 var query = e.target.value.toLowerCase();
715 var match = [];
715 var match = [];
716 var matches = 0;
716 var matches = 0;
717 var matches_max = 20;
717 var matches_max = 20;
718 if (query != ""){
718 if (query != ""){
719 for(var i=0;i<nodes.length;i++){
719 for(var i=0;i<nodes.length;i++){
720
720
721 var pos = nodes[i].name.toLowerCase().indexOf(query)
721 var pos = nodes[i].name.toLowerCase().indexOf(query)
722 if(query && pos != -1){
722 if(query && pos != -1){
723
723
724 matches++
724 matches++
725 //show only certain amount to not kill browser
725 //show only certain amount to not kill browser
726 if (matches > matches_max){
726 if (matches > matches_max){
727 break;
727 break;
728 }
728 }
729
729
730 var n = nodes[i].name;
730 var n = nodes[i].name;
731 var t = nodes[i].type;
731 var t = nodes[i].type;
732 var n_hl = n.substring(0,pos)
732 var n_hl = n.substring(0,pos)
733 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
733 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
734 +n.substring(pos+query.length)
734 +n.substring(pos+query.length)
735 node_url = node_url.replace('__FPATH__',n);
735 node_url = node_url.replace('__FPATH__',n);
736 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,node_url,n_hl));
736 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,node_url,n_hl));
737 }
737 }
738 if(match.length >= matches_max){
738 if(match.length >= matches_max){
739 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['search truncated']));
739 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['search truncated']));
740 }
740 }
741 }
741 }
742 }
742 }
743 if(query != ""){
743 if(query != ""){
744 YUD.setStyle('tbody','display','none');
744 YUD.setStyle('tbody','display','none');
745 YUD.setStyle('tbody_filtered','display','');
745 YUD.setStyle('tbody_filtered','display','');
746
746
747 if (match.length==0){
747 if (match.length==0){
748 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['no matching files']));
748 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['no matching files']));
749 }
749 }
750
750
751 YUD.get('tbody_filtered').innerHTML = match.join("");
751 YUD.get('tbody_filtered').innerHTML = match.join("");
752 }
752 }
753 else{
753 else{
754 YUD.setStyle('tbody','display','');
754 YUD.setStyle('tbody','display','');
755 YUD.setStyle('tbody_filtered','display','none');
755 YUD.setStyle('tbody_filtered','display','none');
756 }
756 }
757
757
758 }
758 }
759 };
759 };
760
760
761 YUE.on(YUD.get('filter_activate'),'click',function(){
761 YUE.on(YUD.get('filter_activate'),'click',function(){
762 F.initFilter();
762 F.initFilter();
763 })
763 })
764 YUE.on(n_filter,'click',function(){
764 YUE.on(n_filter,'click',function(){
765 if(YUD.hasClass(n_filter,'init')){
765 if(YUD.hasClass(n_filter,'init')){
766 n_filter.value = '';
766 n_filter.value = '';
767 YUD.removeClass(n_filter,'init');
767 YUD.removeClass(n_filter,'init');
768 }
768 }
769 });
769 });
770 YUE.on(n_filter,'keyup',function(e){
770 YUE.on(n_filter,'keyup',function(e){
771 clearTimeout(F.filterTimeout);
771 clearTimeout(F.filterTimeout);
772 F.filterTimeout = setTimeout(F.updateFilter(e),600);
772 F.filterTimeout = setTimeout(F.updateFilter(e),600);
773 });
773 });
774 };
774 };
775
775
776
776
777 var initCodeMirror = function(textAreadId,resetUrl){
777 var initCodeMirror = function(textAreadId,resetUrl){
778 var myCodeMirror = CodeMirror.fromTextArea(YUD.get(textAreadId),{
778 var myCodeMirror = CodeMirror.fromTextArea(YUD.get(textAreadId),{
779 mode: "null",
779 mode: "null",
780 lineNumbers:true
780 lineNumbers:true
781 });
781 });
782 YUE.on('reset','click',function(e){
782 YUE.on('reset','click',function(e){
783 window.location=resetUrl
783 window.location=resetUrl
784 });
784 });
785
785
786 YUE.on('file_enable','click',function(){
786 YUE.on('file_enable','click',function(){
787 YUD.setStyle('editor_container','display','');
787 YUD.setStyle('editor_container','display','');
788 YUD.setStyle('upload_file_container','display','none');
788 YUD.setStyle('upload_file_container','display','none');
789 YUD.setStyle('filename_container','display','');
789 YUD.setStyle('filename_container','display','');
790 });
790 });
791
791
792 YUE.on('upload_file_enable','click',function(){
792 YUE.on('upload_file_enable','click',function(){
793 YUD.setStyle('editor_container','display','none');
793 YUD.setStyle('editor_container','display','none');
794 YUD.setStyle('upload_file_container','display','');
794 YUD.setStyle('upload_file_container','display','');
795 YUD.setStyle('filename_container','display','none');
795 YUD.setStyle('filename_container','display','none');
796 });
796 });
797 };
797 };
798
798
799
799
800
800
801 var getIdentNode = function(n){
801 var getIdentNode = function(n){
802 //iterate thru nodes untill matched interesting node !
802 //iterate thru nodes untill matched interesting node !
803
803
804 if (typeof n == 'undefined'){
804 if (typeof n == 'undefined'){
805 return -1
805 return -1
806 }
806 }
807
807
808 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
808 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
809 return n
809 return n
810 }
810 }
811 else{
811 else{
812 return getIdentNode(n.parentNode);
812 return getIdentNode(n.parentNode);
813 }
813 }
814 };
814 };
815
815
816 var getSelectionLink = function(selection_link_label) {
816 var getSelectionLink = function(selection_link_label) {
817 return function(){
817 return function(){
818 //get selection from start/to nodes
818 //get selection from start/to nodes
819 if (typeof window.getSelection != "undefined") {
819 if (typeof window.getSelection != "undefined") {
820 s = window.getSelection();
820 s = window.getSelection();
821
821
822 from = getIdentNode(s.anchorNode);
822 from = getIdentNode(s.anchorNode);
823 till = getIdentNode(s.focusNode);
823 till = getIdentNode(s.focusNode);
824
824
825 f_int = parseInt(from.id.replace('L',''));
825 f_int = parseInt(from.id.replace('L',''));
826 t_int = parseInt(till.id.replace('L',''));
826 t_int = parseInt(till.id.replace('L',''));
827
827
828 if (f_int > t_int){
828 if (f_int > t_int){
829 //highlight from bottom
829 //highlight from bottom
830 offset = -35;
830 offset = -35;
831 ranges = [t_int,f_int];
831 ranges = [t_int,f_int];
832
832
833 }
833 }
834 else{
834 else{
835 //highligth from top
835 //highligth from top
836 offset = 35;
836 offset = 35;
837 ranges = [f_int,t_int];
837 ranges = [f_int,t_int];
838 }
838 }
839
839
840 if (ranges[0] != ranges[1]){
840 if (ranges[0] != ranges[1]){
841 if(YUD.get('linktt') == null){
841 if(YUD.get('linktt') == null){
842 hl_div = document.createElement('div');
842 hl_div = document.createElement('div');
843 hl_div.id = 'linktt';
843 hl_div.id = 'linktt';
844 }
844 }
845 anchor = '#L'+ranges[0]+'-'+ranges[1];
845 anchor = '#L'+ranges[0]+'-'+ranges[1];
846 hl_div.innerHTML = '';
846 hl_div.innerHTML = '';
847 l = document.createElement('a');
847 l = document.createElement('a');
848 l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
848 l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
849 l.innerHTML = selection_link_label;
849 l.innerHTML = selection_link_label;
850 hl_div.appendChild(l);
850 hl_div.appendChild(l);
851
851
852 YUD.get('body').appendChild(hl_div);
852 YUD.get('body').appendChild(hl_div);
853
853
854 xy = YUD.getXY(till.id);
854 xy = YUD.getXY(till.id);
855
855
856 YUD.addClass('linktt','yui-tt');
856 YUD.addClass('linktt','yui-tt');
857 YUD.setStyle('linktt','top',xy[1]+offset+'px');
857 YUD.setStyle('linktt','top',xy[1]+offset+'px');
858 YUD.setStyle('linktt','left',xy[0]+'px');
858 YUD.setStyle('linktt','left',xy[0]+'px');
859 YUD.setStyle('linktt','visibility','visible');
859 YUD.setStyle('linktt','visibility','visible');
860 }
860 }
861 else{
861 else{
862 YUD.setStyle('linktt','visibility','hidden');
862 YUD.setStyle('linktt','visibility','hidden');
863 }
863 }
864 }
864 }
865 }
865 }
866 };
866 };
867
867
868 var deleteNotification = function(url, notification_id,callbacks){
868 var deleteNotification = function(url, notification_id,callbacks){
869 var callback = {
869 var callback = {
870 success:function(o){
870 success:function(o){
871 var obj = YUD.get(String("notification_"+notification_id));
871 var obj = YUD.get(String("notification_"+notification_id));
872 if(obj.parentNode !== undefined){
872 if(obj.parentNode !== undefined){
873 obj.parentNode.removeChild(obj);
873 obj.parentNode.removeChild(obj);
874 }
874 }
875 _run_callbacks(callbacks);
875 _run_callbacks(callbacks);
876 },
876 },
877 failure:function(o){
877 failure:function(o){
878 alert("error");
878 alert("error");
879 },
879 },
880 };
880 };
881 var postData = '_method=delete';
881 var postData = '_method=delete';
882 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
882 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
883 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
883 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
884 callback, postData);
884 callback, postData);
885 };
885 };
886
886
887 var readNotification = function(url, notification_id,callbacks){
887 var readNotification = function(url, notification_id,callbacks){
888 var callback = {
888 var callback = {
889 success:function(o){
889 success:function(o){
890 var obj = YUD.get(String("notification_"+notification_id));
890 var obj = YUD.get(String("notification_"+notification_id));
891 YUD.removeClass(obj, 'unread');
891 YUD.removeClass(obj, 'unread');
892 var r_button = YUD.getElementsByClassName('read-notification',null,obj.children[0])[0];
892 var r_button = YUD.getElementsByClassName('read-notification',null,obj.children[0])[0];
893
893
894 if(r_button.parentNode !== undefined){
894 if(r_button.parentNode !== undefined){
895 r_button.parentNode.removeChild(r_button);
895 r_button.parentNode.removeChild(r_button);
896 }
896 }
897 _run_callbacks(callbacks);
897 _run_callbacks(callbacks);
898 },
898 },
899 failure:function(o){
899 failure:function(o){
900 alert("error");
900 alert("error");
901 },
901 },
902 };
902 };
903 var postData = '_method=put';
903 var postData = '_method=put';
904 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
904 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
905 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
905 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
906 callback, postData);
906 callback, postData);
907 };
907 };
908
908
909 /** MEMBERS AUTOCOMPLETE WIDGET **/
909 /** MEMBERS AUTOCOMPLETE WIDGET **/
910
910
911 var MembersAutoComplete = function (users_list, groups_list) {
911 var MembersAutoComplete = function (divid, cont, users_list, groups_list) {
912 var myUsers = users_list;
912 var myUsers = users_list;
913 var myGroups = groups_list;
913 var myGroups = groups_list;
914
914
915 // Define a custom search function for the DataSource of users
915 // Define a custom search function for the DataSource of users
916 var matchUsers = function (sQuery) {
916 var matchUsers = function (sQuery) {
917 // Case insensitive matching
917 // Case insensitive matching
918 var query = sQuery.toLowerCase();
918 var query = sQuery.toLowerCase();
919 var i = 0;
919 var i = 0;
920 var l = myUsers.length;
920 var l = myUsers.length;
921 var matches = [];
921 var matches = [];
922
922
923 // Match against each name of each contact
923 // Match against each name of each contact
924 for (; i < l; i++) {
924 for (; i < l; i++) {
925 contact = myUsers[i];
925 contact = myUsers[i];
926 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
926 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
927 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
927 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
928 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
928 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
929 matches[matches.length] = contact;
929 matches[matches.length] = contact;
930 }
930 }
931 }
931 }
932 return matches;
932 return matches;
933 };
933 };
934
934
935 // Define a custom search function for the DataSource of usersGroups
935 // Define a custom search function for the DataSource of usersGroups
936 var matchGroups = function (sQuery) {
936 var matchGroups = function (sQuery) {
937 // Case insensitive matching
937 // Case insensitive matching
938 var query = sQuery.toLowerCase();
938 var query = sQuery.toLowerCase();
939 var i = 0;
939 var i = 0;
940 var l = myGroups.length;
940 var l = myGroups.length;
941 var matches = [];
941 var matches = [];
942
942
943 // Match against each name of each contact
943 // Match against each name of each contact
944 for (; i < l; i++) {
944 for (; i < l; i++) {
945 matched_group = myGroups[i];
945 matched_group = myGroups[i];
946 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
946 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
947 matches[matches.length] = matched_group;
947 matches[matches.length] = matched_group;
948 }
948 }
949 }
949 }
950 return matches;
950 return matches;
951 };
951 };
952
952
953 //match all
953 //match all
954 var matchAll = function (sQuery) {
954 var matchAll = function (sQuery) {
955 u = matchUsers(sQuery);
955 u = matchUsers(sQuery);
956 g = matchGroups(sQuery);
956 g = matchGroups(sQuery);
957 return u.concat(g);
957 return u.concat(g);
958 };
958 };
959
959
960 // DataScheme for members
960 // DataScheme for members
961 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
961 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
962 memberDS.responseSchema = {
962 memberDS.responseSchema = {
963 fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
963 fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
964 };
964 };
965
965
966 // DataScheme for owner
966 // DataScheme for owner
967 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
967 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
968 ownerDS.responseSchema = {
968 ownerDS.responseSchema = {
969 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
969 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
970 };
970 };
971
971
972 // Instantiate AutoComplete for perms
972 // Instantiate AutoComplete for perms
973 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
973 var membersAC = new YAHOO.widget.AutoComplete(divid, cont, memberDS);
974 membersAC.useShadow = false;
974 membersAC.useShadow = false;
975 membersAC.resultTypeList = false;
975 membersAC.resultTypeList = false;
976 membersAC.animVert = false;
976 membersAC.animVert = false;
977 membersAC.animHoriz = false;
977 membersAC.animHoriz = false;
978 membersAC.animSpeed = 0.1;
978 membersAC.animSpeed = 0.1;
979
979
980 // Instantiate AutoComplete for owner
980 // Instantiate AutoComplete for owner
981 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
981 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
982 ownerAC.useShadow = false;
982 ownerAC.useShadow = false;
983 ownerAC.resultTypeList = false;
983 ownerAC.resultTypeList = false;
984 ownerAC.animVert = false;
984 ownerAC.animVert = false;
985 ownerAC.animHoriz = false;
985 ownerAC.animHoriz = false;
986 ownerAC.animSpeed = 0.1;
986 ownerAC.animSpeed = 0.1;
987
987
988 // Helper highlight function for the formatter
988 // Helper highlight function for the formatter
989 var highlightMatch = function (full, snippet, matchindex) {
989 var highlightMatch = function (full, snippet, matchindex) {
990 return full.substring(0, matchindex)
990 return full.substring(0, matchindex)
991 + "<span class='match'>"
991 + "<span class='match'>"
992 + full.substr(matchindex, snippet.length)
992 + full.substr(matchindex, snippet.length)
993 + "</span>" + full.substring(matchindex + snippet.length);
993 + "</span>" + full.substring(matchindex + snippet.length);
994 };
994 };
995
995
996 // Custom formatter to highlight the matching letters
996 // Custom formatter to highlight the matching letters
997 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
997 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
998 var query = sQuery.toLowerCase();
998 var query = sQuery.toLowerCase();
999 var _gravatar = function(res, em, group){
999 var _gravatar = function(res, em, group){
1000 if (group !== undefined){
1000 if (group !== undefined){
1001 em = '/images/icons/group.png'
1001 em = '/images/icons/group.png'
1002 }
1002 }
1003 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1003 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1004 return tmpl.format(em,res)
1004 return tmpl.format(em,res)
1005 }
1005 }
1006 // group
1006 // group
1007 if (oResultData.grname != undefined) {
1007 if (oResultData.grname != undefined) {
1008 var grname = oResultData.grname;
1008 var grname = oResultData.grname;
1009 var grmembers = oResultData.grmembers;
1009 var grmembers = oResultData.grmembers;
1010 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
1010 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
1011 var grprefix = "{0}: ".format(_TM['Group']);
1011 var grprefix = "{0}: ".format(_TM['Group']);
1012 var grsuffix = " (" + grmembers + " )";
1012 var grsuffix = " (" + grmembers + " )";
1013 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
1013 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
1014
1014
1015 if (grnameMatchIndex > -1) {
1015 if (grnameMatchIndex > -1) {
1016 return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true);
1016 return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true);
1017 }
1017 }
1018 return _gravatar(grprefix + oResultData.grname + grsuffix, null,true);
1018 return _gravatar(grprefix + oResultData.grname + grsuffix, null,true);
1019 // Users
1019 // Users
1020 } else if (oResultData.nname != undefined) {
1020 } else if (oResultData.nname != undefined) {
1021 var fname = oResultData.fname || "";
1021 var fname = oResultData.fname || "";
1022 var lname = oResultData.lname || "";
1022 var lname = oResultData.lname || "";
1023 var nname = oResultData.nname;
1023 var nname = oResultData.nname;
1024
1024
1025 // Guard against null value
1025 // Guard against null value
1026 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1026 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1027 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1027 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1028 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1028 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1029 displayfname, displaylname, displaynname;
1029 displayfname, displaylname, displaynname;
1030
1030
1031 if (fnameMatchIndex > -1) {
1031 if (fnameMatchIndex > -1) {
1032 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1032 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1033 } else {
1033 } else {
1034 displayfname = fname;
1034 displayfname = fname;
1035 }
1035 }
1036
1036
1037 if (lnameMatchIndex > -1) {
1037 if (lnameMatchIndex > -1) {
1038 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1038 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1039 } else {
1039 } else {
1040 displaylname = lname;
1040 displaylname = lname;
1041 }
1041 }
1042
1042
1043 if (nnameMatchIndex > -1) {
1043 if (nnameMatchIndex > -1) {
1044 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1044 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1045 } else {
1045 } else {
1046 displaynname = nname ? "(" + nname + ")" : "";
1046 displaynname = nname ? "(" + nname + ")" : "";
1047 }
1047 }
1048
1048
1049 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1049 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1050 } else {
1050 } else {
1051 return '';
1051 return '';
1052 }
1052 }
1053 };
1053 };
1054 membersAC.formatResult = custom_formatter;
1054 membersAC.formatResult = custom_formatter;
1055 ownerAC.formatResult = custom_formatter;
1055 ownerAC.formatResult = custom_formatter;
1056
1056
1057 var myHandler = function (sType, aArgs) {
1057 var myHandler = function (sType, aArgs) {
1058
1058 var nextId = divid.split('perm_new_member_name_')[1];
1059 var myAC = aArgs[0]; // reference back to the AC instance
1059 var myAC = aArgs[0]; // reference back to the AC instance
1060 var elLI = aArgs[1]; // reference to the selected LI element
1060 var elLI = aArgs[1]; // reference to the selected LI element
1061 var oData = aArgs[2]; // object literal of selected item's result data
1061 var oData = aArgs[2]; // object literal of selected item's result data
1062 //fill the autocomplete with value
1062 //fill the autocomplete with value
1063 if (oData.nname != undefined) {
1063 if (oData.nname != undefined) {
1064 //users
1064 //users
1065 myAC.getInputEl().value = oData.nname;
1065 myAC.getInputEl().value = oData.nname;
1066 YUD.get('perm_new_member_type').value = 'user';
1066 YUD.get('perm_new_member_type_'+nextId).value = 'user';
1067 } else {
1067 } else {
1068 //groups
1068 //groups
1069 myAC.getInputEl().value = oData.grname;
1069 myAC.getInputEl().value = oData.grname;
1070 YUD.get('perm_new_member_type').value = 'users_group';
1070 YUD.get('perm_new_member_type_'+nextId).value = 'users_group';
1071 }
1071 }
1072 };
1072 };
1073
1073
1074 membersAC.itemSelectEvent.subscribe(myHandler);
1074 membersAC.itemSelectEvent.subscribe(myHandler);
1075 if(ownerAC.itemSelectEvent){
1075 if(ownerAC.itemSelectEvent){
1076 ownerAC.itemSelectEvent.subscribe(myHandler);
1076 ownerAC.itemSelectEvent.subscribe(myHandler);
1077 }
1077 }
1078
1078
1079 return {
1079 return {
1080 memberDS: memberDS,
1080 memberDS: memberDS,
1081 ownerDS: ownerDS,
1081 ownerDS: ownerDS,
1082 membersAC: membersAC,
1082 membersAC: membersAC,
1083 ownerAC: ownerAC,
1083 ownerAC: ownerAC,
1084 };
1084 };
1085 }
1085 }
1086
1086
1087
1087
1088 var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
1088 var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
1089 var myUsers = users_list;
1089 var myUsers = users_list;
1090 var myGroups = groups_list;
1090 var myGroups = groups_list;
1091
1091
1092 // Define a custom search function for the DataSource of users
1092 // Define a custom search function for the DataSource of users
1093 var matchUsers = function (sQuery) {
1093 var matchUsers = function (sQuery) {
1094 var org_sQuery = sQuery;
1094 var org_sQuery = sQuery;
1095 if(this.mentionQuery == null){
1095 if(this.mentionQuery == null){
1096 return []
1096 return []
1097 }
1097 }
1098 sQuery = this.mentionQuery;
1098 sQuery = this.mentionQuery;
1099 // Case insensitive matching
1099 // Case insensitive matching
1100 var query = sQuery.toLowerCase();
1100 var query = sQuery.toLowerCase();
1101 var i = 0;
1101 var i = 0;
1102 var l = myUsers.length;
1102 var l = myUsers.length;
1103 var matches = [];
1103 var matches = [];
1104
1104
1105 // Match against each name of each contact
1105 // Match against each name of each contact
1106 for (; i < l; i++) {
1106 for (; i < l; i++) {
1107 contact = myUsers[i];
1107 contact = myUsers[i];
1108 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1108 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1109 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1109 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1110 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1110 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1111 matches[matches.length] = contact;
1111 matches[matches.length] = contact;
1112 }
1112 }
1113 }
1113 }
1114 return matches
1114 return matches
1115 };
1115 };
1116
1116
1117 //match all
1117 //match all
1118 var matchAll = function (sQuery) {
1118 var matchAll = function (sQuery) {
1119 u = matchUsers(sQuery);
1119 u = matchUsers(sQuery);
1120 return u
1120 return u
1121 };
1121 };
1122
1122
1123 // DataScheme for owner
1123 // DataScheme for owner
1124 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1124 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1125
1125
1126 ownerDS.responseSchema = {
1126 ownerDS.responseSchema = {
1127 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1127 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1128 };
1128 };
1129
1129
1130 // Instantiate AutoComplete for mentions
1130 // Instantiate AutoComplete for mentions
1131 var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1131 var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1132 ownerAC.useShadow = false;
1132 ownerAC.useShadow = false;
1133 ownerAC.resultTypeList = false;
1133 ownerAC.resultTypeList = false;
1134 ownerAC.suppressInputUpdate = true;
1134 ownerAC.suppressInputUpdate = true;
1135 ownerAC.animVert = false;
1135 ownerAC.animVert = false;
1136 ownerAC.animHoriz = false;
1136 ownerAC.animHoriz = false;
1137 ownerAC.animSpeed = 0.1;
1137 ownerAC.animSpeed = 0.1;
1138
1138
1139 // Helper highlight function for the formatter
1139 // Helper highlight function for the formatter
1140 var highlightMatch = function (full, snippet, matchindex) {
1140 var highlightMatch = function (full, snippet, matchindex) {
1141 return full.substring(0, matchindex)
1141 return full.substring(0, matchindex)
1142 + "<span class='match'>"
1142 + "<span class='match'>"
1143 + full.substr(matchindex, snippet.length)
1143 + full.substr(matchindex, snippet.length)
1144 + "</span>" + full.substring(matchindex + snippet.length);
1144 + "</span>" + full.substring(matchindex + snippet.length);
1145 };
1145 };
1146
1146
1147 // Custom formatter to highlight the matching letters
1147 // Custom formatter to highlight the matching letters
1148 ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1148 ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1149 var org_sQuery = sQuery;
1149 var org_sQuery = sQuery;
1150 if(this.dataSource.mentionQuery != null){
1150 if(this.dataSource.mentionQuery != null){
1151 sQuery = this.dataSource.mentionQuery;
1151 sQuery = this.dataSource.mentionQuery;
1152 }
1152 }
1153
1153
1154 var query = sQuery.toLowerCase();
1154 var query = sQuery.toLowerCase();
1155 var _gravatar = function(res, em, group){
1155 var _gravatar = function(res, em, group){
1156 if (group !== undefined){
1156 if (group !== undefined){
1157 em = '/images/icons/group.png'
1157 em = '/images/icons/group.png'
1158 }
1158 }
1159 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1159 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1160 return tmpl.format(em,res)
1160 return tmpl.format(em,res)
1161 }
1161 }
1162 if (oResultData.nname != undefined) {
1162 if (oResultData.nname != undefined) {
1163 var fname = oResultData.fname || "";
1163 var fname = oResultData.fname || "";
1164 var lname = oResultData.lname || "";
1164 var lname = oResultData.lname || "";
1165 var nname = oResultData.nname;
1165 var nname = oResultData.nname;
1166
1166
1167 // Guard against null value
1167 // Guard against null value
1168 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1168 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1169 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1169 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1170 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1170 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1171 displayfname, displaylname, displaynname;
1171 displayfname, displaylname, displaynname;
1172
1172
1173 if (fnameMatchIndex > -1) {
1173 if (fnameMatchIndex > -1) {
1174 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1174 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1175 } else {
1175 } else {
1176 displayfname = fname;
1176 displayfname = fname;
1177 }
1177 }
1178
1178
1179 if (lnameMatchIndex > -1) {
1179 if (lnameMatchIndex > -1) {
1180 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1180 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1181 } else {
1181 } else {
1182 displaylname = lname;
1182 displaylname = lname;
1183 }
1183 }
1184
1184
1185 if (nnameMatchIndex > -1) {
1185 if (nnameMatchIndex > -1) {
1186 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1186 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1187 } else {
1187 } else {
1188 displaynname = nname ? "(" + nname + ")" : "";
1188 displaynname = nname ? "(" + nname + ")" : "";
1189 }
1189 }
1190
1190
1191 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1191 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1192 } else {
1192 } else {
1193 return '';
1193 return '';
1194 }
1194 }
1195 };
1195 };
1196
1196
1197 if(ownerAC.itemSelectEvent){
1197 if(ownerAC.itemSelectEvent){
1198 ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1198 ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1199
1199
1200 var myAC = aArgs[0]; // reference back to the AC instance
1200 var myAC = aArgs[0]; // reference back to the AC instance
1201 var elLI = aArgs[1]; // reference to the selected LI element
1201 var elLI = aArgs[1]; // reference to the selected LI element
1202 var oData = aArgs[2]; // object literal of selected item's result data
1202 var oData = aArgs[2]; // object literal of selected item's result data
1203 //fill the autocomplete with value
1203 //fill the autocomplete with value
1204 if (oData.nname != undefined) {
1204 if (oData.nname != undefined) {
1205 //users
1205 //users
1206 //Replace the mention name with replaced
1206 //Replace the mention name with replaced
1207 var re = new RegExp();
1207 var re = new RegExp();
1208 var org = myAC.getInputEl().value;
1208 var org = myAC.getInputEl().value;
1209 var chunks = myAC.dataSource.chunks
1209 var chunks = myAC.dataSource.chunks
1210 // replace middle chunk(the search term) with actuall match
1210 // replace middle chunk(the search term) with actuall match
1211 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1211 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1212 '@'+oData.nname+' ');
1212 '@'+oData.nname+' ');
1213 myAC.getInputEl().value = chunks.join('')
1213 myAC.getInputEl().value = chunks.join('')
1214 YUD.get(myAC.getInputEl()).focus(); // Y U NO WORK !?
1214 YUD.get(myAC.getInputEl()).focus(); // Y U NO WORK !?
1215 } else {
1215 } else {
1216 //groups
1216 //groups
1217 myAC.getInputEl().value = oData.grname;
1217 myAC.getInputEl().value = oData.grname;
1218 YUD.get('perm_new_member_type').value = 'users_group';
1218 YUD.get('perm_new_member_type').value = 'users_group';
1219 }
1219 }
1220 });
1220 });
1221 }
1221 }
1222
1222
1223 // in this keybuffer we will gather current value of search !
1223 // in this keybuffer we will gather current value of search !
1224 // since we need to get this just when someone does `@` then we do the
1224 // since we need to get this just when someone does `@` then we do the
1225 // search
1225 // search
1226 ownerAC.dataSource.chunks = [];
1226 ownerAC.dataSource.chunks = [];
1227 ownerAC.dataSource.mentionQuery = null;
1227 ownerAC.dataSource.mentionQuery = null;
1228
1228
1229 ownerAC.get_mention = function(msg, max_pos) {
1229 ownerAC.get_mention = function(msg, max_pos) {
1230 var org = msg;
1230 var org = msg;
1231 var re = new RegExp('(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)$')
1231 var re = new RegExp('(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)$')
1232 var chunks = [];
1232 var chunks = [];
1233
1233
1234
1234
1235 // cut first chunk until curret pos
1235 // cut first chunk until curret pos
1236 var to_max = msg.substr(0, max_pos);
1236 var to_max = msg.substr(0, max_pos);
1237 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1237 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1238 var msg2 = to_max.substr(at_pos);
1238 var msg2 = to_max.substr(at_pos);
1239
1239
1240 chunks.push(org.substr(0,at_pos))// prefix chunk
1240 chunks.push(org.substr(0,at_pos))// prefix chunk
1241 chunks.push(msg2) // search chunk
1241 chunks.push(msg2) // search chunk
1242 chunks.push(org.substr(max_pos)) // postfix chunk
1242 chunks.push(org.substr(max_pos)) // postfix chunk
1243
1243
1244 // clean up msg2 for filtering and regex match
1244 // clean up msg2 for filtering and regex match
1245 var msg2 = msg2.lstrip(' ').lstrip('\n');
1245 var msg2 = msg2.lstrip(' ').lstrip('\n');
1246
1246
1247 if(re.test(msg2)){
1247 if(re.test(msg2)){
1248 var unam = re.exec(msg2)[1];
1248 var unam = re.exec(msg2)[1];
1249 return [unam, chunks];
1249 return [unam, chunks];
1250 }
1250 }
1251 return [null, null];
1251 return [null, null];
1252 };
1252 };
1253
1253
1254 if (ownerAC.textboxKeyUpEvent){
1254 if (ownerAC.textboxKeyUpEvent){
1255 ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
1255 ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
1256
1256
1257 var ac_obj = args[0];
1257 var ac_obj = args[0];
1258 var currentMessage = args[1];
1258 var currentMessage = args[1];
1259 var currentCaretPosition = args[0]._elTextbox.selectionStart;
1259 var currentCaretPosition = args[0]._elTextbox.selectionStart;
1260
1260
1261 var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
1261 var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
1262 var curr_search = null;
1262 var curr_search = null;
1263 if(unam[0]){
1263 if(unam[0]){
1264 curr_search = unam[0];
1264 curr_search = unam[0];
1265 }
1265 }
1266
1266
1267 ownerAC.dataSource.chunks = unam[1];
1267 ownerAC.dataSource.chunks = unam[1];
1268 ownerAC.dataSource.mentionQuery = curr_search;
1268 ownerAC.dataSource.mentionQuery = curr_search;
1269
1269
1270 })
1270 })
1271 }
1271 }
1272 return {
1272 return {
1273 ownerDS: ownerDS,
1273 ownerDS: ownerDS,
1274 ownerAC: ownerAC,
1274 ownerAC: ownerAC,
1275 };
1275 };
1276 }
1276 }
1277
1277
1278
1278
1279 var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) {
1279 var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) {
1280 var myUsers = users_list;
1280 var myUsers = users_list;
1281 var myGroups = groups_list;
1281 var myGroups = groups_list;
1282
1282
1283 // Define a custom search function for the DataSource of users
1283 // Define a custom search function for the DataSource of users
1284 var matchUsers = function (sQuery) {
1284 var matchUsers = function (sQuery) {
1285 // Case insensitive matching
1285 // Case insensitive matching
1286 var query = sQuery.toLowerCase();
1286 var query = sQuery.toLowerCase();
1287 var i = 0;
1287 var i = 0;
1288 var l = myUsers.length;
1288 var l = myUsers.length;
1289 var matches = [];
1289 var matches = [];
1290
1290
1291 // Match against each name of each contact
1291 // Match against each name of each contact
1292 for (; i < l; i++) {
1292 for (; i < l; i++) {
1293 contact = myUsers[i];
1293 contact = myUsers[i];
1294 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1294 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1295 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1295 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1296 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1296 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1297 matches[matches.length] = contact;
1297 matches[matches.length] = contact;
1298 }
1298 }
1299 }
1299 }
1300 return matches;
1300 return matches;
1301 };
1301 };
1302
1302
1303 // Define a custom search function for the DataSource of usersGroups
1303 // Define a custom search function for the DataSource of usersGroups
1304 var matchGroups = function (sQuery) {
1304 var matchGroups = function (sQuery) {
1305 // Case insensitive matching
1305 // Case insensitive matching
1306 var query = sQuery.toLowerCase();
1306 var query = sQuery.toLowerCase();
1307 var i = 0;
1307 var i = 0;
1308 var l = myGroups.length;
1308 var l = myGroups.length;
1309 var matches = [];
1309 var matches = [];
1310
1310
1311 // Match against each name of each contact
1311 // Match against each name of each contact
1312 for (; i < l; i++) {
1312 for (; i < l; i++) {
1313 matched_group = myGroups[i];
1313 matched_group = myGroups[i];
1314 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1314 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1315 matches[matches.length] = matched_group;
1315 matches[matches.length] = matched_group;
1316 }
1316 }
1317 }
1317 }
1318 return matches;
1318 return matches;
1319 };
1319 };
1320
1320
1321 //match all
1321 //match all
1322 var matchAll = function (sQuery) {
1322 var matchAll = function (sQuery) {
1323 u = matchUsers(sQuery);
1323 u = matchUsers(sQuery);
1324 return u
1324 return u
1325 };
1325 };
1326
1326
1327 // DataScheme for owner
1327 // DataScheme for owner
1328 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1328 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1329
1329
1330 ownerDS.responseSchema = {
1330 ownerDS.responseSchema = {
1331 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1331 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1332 };
1332 };
1333
1333
1334 // Instantiate AutoComplete for mentions
1334 // Instantiate AutoComplete for mentions
1335 var reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1335 var reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1336 reviewerAC.useShadow = false;
1336 reviewerAC.useShadow = false;
1337 reviewerAC.resultTypeList = false;
1337 reviewerAC.resultTypeList = false;
1338 reviewerAC.suppressInputUpdate = true;
1338 reviewerAC.suppressInputUpdate = true;
1339 reviewerAC.animVert = false;
1339 reviewerAC.animVert = false;
1340 reviewerAC.animHoriz = false;
1340 reviewerAC.animHoriz = false;
1341 reviewerAC.animSpeed = 0.1;
1341 reviewerAC.animSpeed = 0.1;
1342
1342
1343 // Helper highlight function for the formatter
1343 // Helper highlight function for the formatter
1344 var highlightMatch = function (full, snippet, matchindex) {
1344 var highlightMatch = function (full, snippet, matchindex) {
1345 return full.substring(0, matchindex)
1345 return full.substring(0, matchindex)
1346 + "<span class='match'>"
1346 + "<span class='match'>"
1347 + full.substr(matchindex, snippet.length)
1347 + full.substr(matchindex, snippet.length)
1348 + "</span>" + full.substring(matchindex + snippet.length);
1348 + "</span>" + full.substring(matchindex + snippet.length);
1349 };
1349 };
1350
1350
1351 // Custom formatter to highlight the matching letters
1351 // Custom formatter to highlight the matching letters
1352 reviewerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1352 reviewerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1353 var org_sQuery = sQuery;
1353 var org_sQuery = sQuery;
1354 if(this.dataSource.mentionQuery != null){
1354 if(this.dataSource.mentionQuery != null){
1355 sQuery = this.dataSource.mentionQuery;
1355 sQuery = this.dataSource.mentionQuery;
1356 }
1356 }
1357
1357
1358 var query = sQuery.toLowerCase();
1358 var query = sQuery.toLowerCase();
1359 var _gravatar = function(res, em, group){
1359 var _gravatar = function(res, em, group){
1360 if (group !== undefined){
1360 if (group !== undefined){
1361 em = '/images/icons/group.png'
1361 em = '/images/icons/group.png'
1362 }
1362 }
1363 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1363 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1364 return tmpl.format(em,res)
1364 return tmpl.format(em,res)
1365 }
1365 }
1366 if (oResultData.nname != undefined) {
1366 if (oResultData.nname != undefined) {
1367 var fname = oResultData.fname || "";
1367 var fname = oResultData.fname || "";
1368 var lname = oResultData.lname || "";
1368 var lname = oResultData.lname || "";
1369 var nname = oResultData.nname;
1369 var nname = oResultData.nname;
1370
1370
1371 // Guard against null value
1371 // Guard against null value
1372 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1372 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1373 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1373 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1374 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1374 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1375 displayfname, displaylname, displaynname;
1375 displayfname, displaylname, displaynname;
1376
1376
1377 if (fnameMatchIndex > -1) {
1377 if (fnameMatchIndex > -1) {
1378 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1378 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1379 } else {
1379 } else {
1380 displayfname = fname;
1380 displayfname = fname;
1381 }
1381 }
1382
1382
1383 if (lnameMatchIndex > -1) {
1383 if (lnameMatchIndex > -1) {
1384 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1384 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1385 } else {
1385 } else {
1386 displaylname = lname;
1386 displaylname = lname;
1387 }
1387 }
1388
1388
1389 if (nnameMatchIndex > -1) {
1389 if (nnameMatchIndex > -1) {
1390 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1390 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1391 } else {
1391 } else {
1392 displaynname = nname ? "(" + nname + ")" : "";
1392 displaynname = nname ? "(" + nname + ")" : "";
1393 }
1393 }
1394
1394
1395 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1395 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1396 } else {
1396 } else {
1397 return '';
1397 return '';
1398 }
1398 }
1399 };
1399 };
1400
1400
1401 //members cache to catch duplicates
1401 //members cache to catch duplicates
1402 reviewerAC.dataSource.cache = [];
1402 reviewerAC.dataSource.cache = [];
1403 // hack into select event
1403 // hack into select event
1404 if(reviewerAC.itemSelectEvent){
1404 if(reviewerAC.itemSelectEvent){
1405 reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1405 reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1406
1406
1407 var myAC = aArgs[0]; // reference back to the AC instance
1407 var myAC = aArgs[0]; // reference back to the AC instance
1408 var elLI = aArgs[1]; // reference to the selected LI element
1408 var elLI = aArgs[1]; // reference to the selected LI element
1409 var oData = aArgs[2]; // object literal of selected item's result data
1409 var oData = aArgs[2]; // object literal of selected item's result data
1410 var members = YUD.get('review_members');
1410 var members = YUD.get('review_members');
1411 //fill the autocomplete with value
1411 //fill the autocomplete with value
1412
1412
1413 if (oData.nname != undefined) {
1413 if (oData.nname != undefined) {
1414 if (myAC.dataSource.cache.indexOf(oData.id) != -1){
1414 if (myAC.dataSource.cache.indexOf(oData.id) != -1){
1415 return
1415 return
1416 }
1416 }
1417
1417
1418 var tmpl = '<li id="reviewer_{2}">'+
1418 var tmpl = '<li id="reviewer_{2}">'+
1419 '<div class="reviewers_member">'+
1419 '<div class="reviewers_member">'+
1420 '<div class="gravatar"><img alt="gravatar" src="{0}"/> </div>'+
1420 '<div class="gravatar"><img alt="gravatar" src="{0}"/> </div>'+
1421 '<div style="float:left">{1}</div>'+
1421 '<div style="float:left">{1}</div>'+
1422 '<input type="hidden" value="{2}" name="review_members" />'+
1422 '<input type="hidden" value="{2}" name="review_members" />'+
1423 '<span class="delete_icon action_button" onclick="removeReviewer({2})"></span>'+
1423 '<span class="delete_icon action_button" onclick="removeReviewer({2})"></span>'+
1424 '</div>'+
1424 '</div>'+
1425 '</li>'
1425 '</li>'
1426
1426
1427 var displayname = "{0} {1} ({2})".format(oData.fname,oData.lname,oData.nname);
1427 var displayname = "{0} {1} ({2})".format(oData.fname,oData.lname,oData.nname);
1428 var element = tmpl.format(oData.gravatar_lnk,displayname,oData.id);
1428 var element = tmpl.format(oData.gravatar_lnk,displayname,oData.id);
1429 members.innerHTML += element;
1429 members.innerHTML += element;
1430 myAC.dataSource.cache.push(oData.id);
1430 myAC.dataSource.cache.push(oData.id);
1431 YUD.get('user').value = ''
1431 YUD.get('user').value = ''
1432 }
1432 }
1433 });
1433 });
1434 }
1434 }
1435 return {
1435 return {
1436 ownerDS: ownerDS,
1436 ownerDS: ownerDS,
1437 reviewerAC: reviewerAC,
1437 reviewerAC: reviewerAC,
1438 };
1438 };
1439 }
1439 }
1440
1440
1441
1441
1442 /**
1442 /**
1443 * QUICK REPO MENU
1443 * QUICK REPO MENU
1444 */
1444 */
1445 var quick_repo_menu = function(){
1445 var quick_repo_menu = function(){
1446 YUE.on(YUQ('.quick_repo_menu'),'mouseenter',function(e){
1446 YUE.on(YUQ('.quick_repo_menu'),'mouseenter',function(e){
1447 var menu = e.currentTarget.firstElementChild.firstElementChild;
1447 var menu = e.currentTarget.firstElementChild.firstElementChild;
1448 if(YUD.hasClass(menu,'hidden')){
1448 if(YUD.hasClass(menu,'hidden')){
1449 YUD.replaceClass(e.currentTarget,'hidden', 'active');
1449 YUD.replaceClass(e.currentTarget,'hidden', 'active');
1450 YUD.replaceClass(menu, 'hidden', 'active');
1450 YUD.replaceClass(menu, 'hidden', 'active');
1451 }
1451 }
1452 })
1452 })
1453 YUE.on(YUQ('.quick_repo_menu'),'mouseleave',function(e){
1453 YUE.on(YUQ('.quick_repo_menu'),'mouseleave',function(e){
1454 var menu = e.currentTarget.firstElementChild.firstElementChild;
1454 var menu = e.currentTarget.firstElementChild.firstElementChild;
1455 if(YUD.hasClass(menu,'active')){
1455 if(YUD.hasClass(menu,'active')){
1456 YUD.replaceClass(e.currentTarget, 'active', 'hidden');
1456 YUD.replaceClass(e.currentTarget, 'active', 'hidden');
1457 YUD.replaceClass(menu, 'active', 'hidden');
1457 YUD.replaceClass(menu, 'active', 'hidden');
1458 }
1458 }
1459 })
1459 })
1460 };
1460 };
1461
1461
1462
1462
1463 /**
1463 /**
1464 * TABLE SORTING
1464 * TABLE SORTING
1465 */
1465 */
1466
1466
1467 // returns a node from given html;
1467 // returns a node from given html;
1468 var fromHTML = function(html){
1468 var fromHTML = function(html){
1469 var _html = document.createElement('element');
1469 var _html = document.createElement('element');
1470 _html.innerHTML = html;
1470 _html.innerHTML = html;
1471 return _html;
1471 return _html;
1472 }
1472 }
1473 var get_rev = function(node){
1473 var get_rev = function(node){
1474 var n = node.firstElementChild.firstElementChild;
1474 var n = node.firstElementChild.firstElementChild;
1475
1475
1476 if (n===null){
1476 if (n===null){
1477 return -1
1477 return -1
1478 }
1478 }
1479 else{
1479 else{
1480 out = n.firstElementChild.innerHTML.split(':')[0].replace('r','');
1480 out = n.firstElementChild.innerHTML.split(':')[0].replace('r','');
1481 return parseInt(out);
1481 return parseInt(out);
1482 }
1482 }
1483 }
1483 }
1484
1484
1485 var get_name = function(node){
1485 var get_name = function(node){
1486 var name = node.firstElementChild.children[2].innerHTML;
1486 var name = node.firstElementChild.children[2].innerHTML;
1487 return name
1487 return name
1488 }
1488 }
1489 var get_group_name = function(node){
1489 var get_group_name = function(node){
1490 var name = node.firstElementChild.children[1].innerHTML;
1490 var name = node.firstElementChild.children[1].innerHTML;
1491 return name
1491 return name
1492 }
1492 }
1493 var get_date = function(node){
1493 var get_date = function(node){
1494 var date_ = YUD.getAttribute(node.firstElementChild,'date');
1494 var date_ = YUD.getAttribute(node.firstElementChild,'date');
1495 return date_
1495 return date_
1496 }
1496 }
1497
1497
1498 var get_age = function(node){
1498 var get_age = function(node){
1499 return node
1499 return node
1500 }
1500 }
1501
1501
1502 var get_link = function(node){
1502 var get_link = function(node){
1503 return node.firstElementChild.text;
1503 return node.firstElementChild.text;
1504 }
1504 }
1505
1505
1506 var revisionSort = function(a, b, desc, field) {
1506 var revisionSort = function(a, b, desc, field) {
1507
1507
1508 var a_ = fromHTML(a.getData(field));
1508 var a_ = fromHTML(a.getData(field));
1509 var b_ = fromHTML(b.getData(field));
1509 var b_ = fromHTML(b.getData(field));
1510
1510
1511 // extract revisions from string nodes
1511 // extract revisions from string nodes
1512 a_ = get_rev(a_)
1512 a_ = get_rev(a_)
1513 b_ = get_rev(b_)
1513 b_ = get_rev(b_)
1514
1514
1515 var comp = YAHOO.util.Sort.compare;
1515 var comp = YAHOO.util.Sort.compare;
1516 var compState = comp(a_, b_, desc);
1516 var compState = comp(a_, b_, desc);
1517 return compState;
1517 return compState;
1518 };
1518 };
1519 var ageSort = function(a, b, desc, field) {
1519 var ageSort = function(a, b, desc, field) {
1520 var a_ = fromHTML(a.getData(field));
1520 var a_ = fromHTML(a.getData(field));
1521 var b_ = fromHTML(b.getData(field));
1521 var b_ = fromHTML(b.getData(field));
1522
1522
1523 // extract name from table
1523 // extract name from table
1524 a_ = get_date(a_)
1524 a_ = get_date(a_)
1525 b_ = get_date(b_)
1525 b_ = get_date(b_)
1526
1526
1527 var comp = YAHOO.util.Sort.compare;
1527 var comp = YAHOO.util.Sort.compare;
1528 var compState = comp(a_, b_, desc);
1528 var compState = comp(a_, b_, desc);
1529 return compState;
1529 return compState;
1530 };
1530 };
1531
1531
1532 var lastLoginSort = function(a, b, desc, field) {
1532 var lastLoginSort = function(a, b, desc, field) {
1533 var a_ = a.getData('last_login_raw') || 0;
1533 var a_ = a.getData('last_login_raw') || 0;
1534 var b_ = b.getData('last_login_raw') || 0;
1534 var b_ = b.getData('last_login_raw') || 0;
1535
1535
1536 var comp = YAHOO.util.Sort.compare;
1536 var comp = YAHOO.util.Sort.compare;
1537 var compState = comp(a_, b_, desc);
1537 var compState = comp(a_, b_, desc);
1538 return compState;
1538 return compState;
1539 };
1539 };
1540
1540
1541 var nameSort = function(a, b, desc, field) {
1541 var nameSort = function(a, b, desc, field) {
1542 var a_ = fromHTML(a.getData(field));
1542 var a_ = fromHTML(a.getData(field));
1543 var b_ = fromHTML(b.getData(field));
1543 var b_ = fromHTML(b.getData(field));
1544
1544
1545 // extract name from table
1545 // extract name from table
1546 a_ = get_name(a_)
1546 a_ = get_name(a_)
1547 b_ = get_name(b_)
1547 b_ = get_name(b_)
1548
1548
1549 var comp = YAHOO.util.Sort.compare;
1549 var comp = YAHOO.util.Sort.compare;
1550 var compState = comp(a_, b_, desc);
1550 var compState = comp(a_, b_, desc);
1551 return compState;
1551 return compState;
1552 };
1552 };
1553
1553
1554 var permNameSort = function(a, b, desc, field) {
1554 var permNameSort = function(a, b, desc, field) {
1555 var a_ = fromHTML(a.getData(field));
1555 var a_ = fromHTML(a.getData(field));
1556 var b_ = fromHTML(b.getData(field));
1556 var b_ = fromHTML(b.getData(field));
1557 // extract name from table
1557 // extract name from table
1558
1558
1559 a_ = a_.children[0].innerHTML;
1559 a_ = a_.children[0].innerHTML;
1560 b_ = b_.children[0].innerHTML;
1560 b_ = b_.children[0].innerHTML;
1561
1561
1562 var comp = YAHOO.util.Sort.compare;
1562 var comp = YAHOO.util.Sort.compare;
1563 var compState = comp(a_, b_, desc);
1563 var compState = comp(a_, b_, desc);
1564 return compState;
1564 return compState;
1565 };
1565 };
1566
1566
1567 var groupNameSort = function(a, b, desc, field) {
1567 var groupNameSort = function(a, b, desc, field) {
1568 var a_ = fromHTML(a.getData(field));
1568 var a_ = fromHTML(a.getData(field));
1569 var b_ = fromHTML(b.getData(field));
1569 var b_ = fromHTML(b.getData(field));
1570
1570
1571 // extract name from table
1571 // extract name from table
1572 a_ = get_group_name(a_)
1572 a_ = get_group_name(a_)
1573 b_ = get_group_name(b_)
1573 b_ = get_group_name(b_)
1574
1574
1575 var comp = YAHOO.util.Sort.compare;
1575 var comp = YAHOO.util.Sort.compare;
1576 var compState = comp(a_, b_, desc);
1576 var compState = comp(a_, b_, desc);
1577 return compState;
1577 return compState;
1578 };
1578 };
1579 var dateSort = function(a, b, desc, field) {
1579 var dateSort = function(a, b, desc, field) {
1580 var a_ = fromHTML(a.getData(field));
1580 var a_ = fromHTML(a.getData(field));
1581 var b_ = fromHTML(b.getData(field));
1581 var b_ = fromHTML(b.getData(field));
1582
1582
1583 // extract name from table
1583 // extract name from table
1584 a_ = get_date(a_)
1584 a_ = get_date(a_)
1585 b_ = get_date(b_)
1585 b_ = get_date(b_)
1586
1586
1587 var comp = YAHOO.util.Sort.compare;
1587 var comp = YAHOO.util.Sort.compare;
1588 var compState = comp(a_, b_, desc);
1588 var compState = comp(a_, b_, desc);
1589 return compState;
1589 return compState;
1590 };
1590 };
1591
1591
1592 var linkSort = function(a, b, desc, field) {
1592 var linkSort = function(a, b, desc, field) {
1593 var a_ = fromHTML(a.getData(field));
1593 var a_ = fromHTML(a.getData(field));
1594 var b_ = fromHTML(a.getData(field));
1594 var b_ = fromHTML(a.getData(field));
1595
1595
1596 // extract url text from string nodes
1596 // extract url text from string nodes
1597 a_ = get_link(a_)
1597 a_ = get_link(a_)
1598 b_ = get_link(b_)
1598 b_ = get_link(b_)
1599
1599
1600 var comp = YAHOO.util.Sort.compare;
1600 var comp = YAHOO.util.Sort.compare;
1601 var compState = comp(a_, b_, desc);
1601 var compState = comp(a_, b_, desc);
1602 return compState;
1602 return compState;
1603 }
1603 }
1604
1604
1605 var addPermAction = function(_html, users_list, groups_list){
1606 var elmts = YUD.getElementsByClassName('last_new_member');
1607 var last_node = elmts[elmts.length-1];
1608 if (last_node){
1609 var next_id = (YUD.getElementsByClassName('new_members')).length;
1610 _html = _html.format(next_id);
1611 last_node.innerHTML = _html;
1612 YUD.setStyle(last_node, 'display', '');
1613 YUD.removeClass(last_node, 'last_new_member');
1614 MembersAutoComplete("perm_new_member_name_"+next_id,
1615 "perm_container_"+next_id, users_list, groups_list);
1616 //create new last NODE
1617 var el = document.createElement('tr');
1618 el.id = 'add_perm_input';
1619 YUD.addClass(el,'last_new_member');
1620 YUD.addClass(el,'new_members');
1621 YUD.insertAfter(el, last_node);
1622 }
1623 }
1605
1624
1606 /* Multi selectors */
1625 /* Multi selectors */
1607
1626
1608 var MultiSelectWidget = function(selected_id, available_id, form_id){
1627 var MultiSelectWidget = function(selected_id, available_id, form_id){
1609
1628
1610
1629
1611 //definition of containers ID's
1630 //definition of containers ID's
1612 var selected_container = selected_id;
1631 var selected_container = selected_id;
1613 var available_container = available_id;
1632 var available_container = available_id;
1614
1633
1615 //temp container for selected storage.
1634 //temp container for selected storage.
1616 var cache = new Array();
1635 var cache = new Array();
1617 var av_cache = new Array();
1636 var av_cache = new Array();
1618 var c = YUD.get(selected_container);
1637 var c = YUD.get(selected_container);
1619 var ac = YUD.get(available_container);
1638 var ac = YUD.get(available_container);
1620
1639
1621 //get only selected options for further fullfilment
1640 //get only selected options for further fullfilment
1622 for(var i = 0;node =c.options[i];i++){
1641 for(var i = 0;node =c.options[i];i++){
1623 if(node.selected){
1642 if(node.selected){
1624 //push selected to my temp storage left overs :)
1643 //push selected to my temp storage left overs :)
1625 cache.push(node);
1644 cache.push(node);
1626 }
1645 }
1627 }
1646 }
1628
1647
1629 //get all available options to cache
1648 //get all available options to cache
1630 for(var i = 0;node =ac.options[i];i++){
1649 for(var i = 0;node =ac.options[i];i++){
1631 //push selected to my temp storage left overs :)
1650 //push selected to my temp storage left overs :)
1632 av_cache.push(node);
1651 av_cache.push(node);
1633 }
1652 }
1634
1653
1635 //fill available only with those not in choosen
1654 //fill available only with those not in choosen
1636 ac.options.length=0;
1655 ac.options.length=0;
1637 tmp_cache = new Array();
1656 tmp_cache = new Array();
1638
1657
1639 for(var i = 0;node = av_cache[i];i++){
1658 for(var i = 0;node = av_cache[i];i++){
1640 var add = true;
1659 var add = true;
1641 for(var i2 = 0;node_2 = cache[i2];i2++){
1660 for(var i2 = 0;node_2 = cache[i2];i2++){
1642 if(node.value == node_2.value){
1661 if(node.value == node_2.value){
1643 add=false;
1662 add=false;
1644 break;
1663 break;
1645 }
1664 }
1646 }
1665 }
1647 if(add){
1666 if(add){
1648 tmp_cache.push(new Option(node.text, node.value, false, false));
1667 tmp_cache.push(new Option(node.text, node.value, false, false));
1649 }
1668 }
1650 }
1669 }
1651
1670
1652 for(var i = 0;node = tmp_cache[i];i++){
1671 for(var i = 0;node = tmp_cache[i];i++){
1653 ac.options[i] = node;
1672 ac.options[i] = node;
1654 }
1673 }
1655
1674
1656 function prompts_action_callback(e){
1675 function prompts_action_callback(e){
1657
1676
1658 var choosen = YUD.get(selected_container);
1677 var choosen = YUD.get(selected_container);
1659 var available = YUD.get(available_container);
1678 var available = YUD.get(available_container);
1660
1679
1661 //get checked and unchecked options from field
1680 //get checked and unchecked options from field
1662 function get_checked(from_field){
1681 function get_checked(from_field){
1663 //temp container for storage.
1682 //temp container for storage.
1664 var sel_cache = new Array();
1683 var sel_cache = new Array();
1665 var oth_cache = new Array();
1684 var oth_cache = new Array();
1666
1685
1667 for(var i = 0;node = from_field.options[i];i++){
1686 for(var i = 0;node = from_field.options[i];i++){
1668 if(node.selected){
1687 if(node.selected){
1669 //push selected fields :)
1688 //push selected fields :)
1670 sel_cache.push(node);
1689 sel_cache.push(node);
1671 }
1690 }
1672 else{
1691 else{
1673 oth_cache.push(node)
1692 oth_cache.push(node)
1674 }
1693 }
1675 }
1694 }
1676
1695
1677 return [sel_cache,oth_cache]
1696 return [sel_cache,oth_cache]
1678 }
1697 }
1679
1698
1680 //fill the field with given options
1699 //fill the field with given options
1681 function fill_with(field,options){
1700 function fill_with(field,options){
1682 //clear firtst
1701 //clear firtst
1683 field.options.length=0;
1702 field.options.length=0;
1684 for(var i = 0;node = options[i];i++){
1703 for(var i = 0;node = options[i];i++){
1685 field.options[i]=new Option(node.text, node.value,
1704 field.options[i]=new Option(node.text, node.value,
1686 false, false);
1705 false, false);
1687 }
1706 }
1688
1707
1689 }
1708 }
1690 //adds to current field
1709 //adds to current field
1691 function add_to(field,options){
1710 function add_to(field,options){
1692 for(var i = 0;node = options[i];i++){
1711 for(var i = 0;node = options[i];i++){
1693 field.appendChild(new Option(node.text, node.value,
1712 field.appendChild(new Option(node.text, node.value,
1694 false, false));
1713 false, false));
1695 }
1714 }
1696 }
1715 }
1697
1716
1698 // add action
1717 // add action
1699 if (this.id=='add_element'){
1718 if (this.id=='add_element'){
1700 var c = get_checked(available);
1719 var c = get_checked(available);
1701 add_to(choosen,c[0]);
1720 add_to(choosen,c[0]);
1702 fill_with(available,c[1]);
1721 fill_with(available,c[1]);
1703 }
1722 }
1704 // remove action
1723 // remove action
1705 if (this.id=='remove_element'){
1724 if (this.id=='remove_element'){
1706 var c = get_checked(choosen);
1725 var c = get_checked(choosen);
1707 add_to(available,c[0]);
1726 add_to(available,c[0]);
1708 fill_with(choosen,c[1]);
1727 fill_with(choosen,c[1]);
1709 }
1728 }
1710 // add all elements
1729 // add all elements
1711 if(this.id=='add_all_elements'){
1730 if(this.id=='add_all_elements'){
1712 for(var i=0; node = available.options[i];i++){
1731 for(var i=0; node = available.options[i];i++){
1713 choosen.appendChild(new Option(node.text,
1732 choosen.appendChild(new Option(node.text,
1714 node.value, false, false));
1733 node.value, false, false));
1715 }
1734 }
1716 available.options.length = 0;
1735 available.options.length = 0;
1717 }
1736 }
1718 //remove all elements
1737 //remove all elements
1719 if(this.id=='remove_all_elements'){
1738 if(this.id=='remove_all_elements'){
1720 for(var i=0; node = choosen.options[i];i++){
1739 for(var i=0; node = choosen.options[i];i++){
1721 available.appendChild(new Option(node.text,
1740 available.appendChild(new Option(node.text,
1722 node.value, false, false));
1741 node.value, false, false));
1723 }
1742 }
1724 choosen.options.length = 0;
1743 choosen.options.length = 0;
1725 }
1744 }
1726
1745
1727 }
1746 }
1728
1747
1729 YUE.addListener(['add_element','remove_element',
1748 YUE.addListener(['add_element','remove_element',
1730 'add_all_elements','remove_all_elements'],'click',
1749 'add_all_elements','remove_all_elements'],'click',
1731 prompts_action_callback)
1750 prompts_action_callback)
1732 if (form_id !== undefined) {
1751 if (form_id !== undefined) {
1733 YUE.addListener(form_id,'submit',function(){
1752 YUE.addListener(form_id,'submit',function(){
1734 var choosen = YUD.get(selected_container);
1753 var choosen = YUD.get(selected_container);
1735 for (var i = 0; i < choosen.options.length; i++) {
1754 for (var i = 0; i < choosen.options.length; i++) {
1736 choosen.options[i].selected = 'selected';
1755 choosen.options[i].selected = 'selected';
1737 }
1756 }
1738 });
1757 });
1739 }
1758 }
1740 }
1759 }
@@ -1,128 +1,128 b''
1 <table id="permissions_manage" class="noborder">
1 <table id="permissions_manage" class="noborder">
2 <tr>
2 <tr>
3 <td>${_('none')}</td>
3 <td>${_('none')}</td>
4 <td>${_('read')}</td>
4 <td>${_('read')}</td>
5 <td>${_('write')}</td>
5 <td>${_('write')}</td>
6 <td>${_('admin')}</td>
6 <td>${_('admin')}</td>
7 <td>${_('member')}</td>
7 <td>${_('member')}</td>
8 <td></td>
8 <td></td>
9 </tr>
9 </tr>
10 ## USERS
10 ## USERS
11 %for r2p in c.repo_info.repo_to_perm:
11 %for r2p in c.repo_info.repo_to_perm:
12 %if r2p.user.username =='default' and c.repo_info.private:
12 %if r2p.user.username =='default' and c.repo_info.private:
13 <tr>
13 <tr>
14 <td colspan="4">
14 <td colspan="4">
15 <span class="private_repo_msg">
15 <span class="private_repo_msg">
16 ${_('private repository')}
16 ${_('private repository')}
17 </span>
17 </span>
18 </td>
18 </td>
19 <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${_('default')}</td>
19 <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${_('default')}</td>
20 </tr>
20 </tr>
21 %else:
21 %else:
22 <tr id="id${id(r2p.user.username)}">
22 <tr id="id${id(r2p.user.username)}">
23 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}</td>
23 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}</td>
24 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}</td>
24 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}</td>
25 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
25 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
26 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
26 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
27 <td style="white-space: nowrap;">
27 <td style="white-space: nowrap;">
28 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
28 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
29 </td>
29 </td>
30 <td>
30 <td>
31 %if r2p.user.username !='default':
31 %if r2p.user.username !='default':
32 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
32 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
33 ${_('revoke')}
33 ${_('revoke')}
34 </span>
34 </span>
35 %endif
35 %endif
36 </td>
36 </td>
37 </tr>
37 </tr>
38 %endif
38 %endif
39 %endfor
39 %endfor
40
40
41 ## USERS GROUPS
41 ## USERS GROUPS
42 %for g2p in c.repo_info.users_group_to_perm:
42 %for g2p in c.repo_info.users_group_to_perm:
43 <tr id="id${id(g2p.users_group.users_group_name)}">
43 <tr id="id${id(g2p.users_group.users_group_name)}">
44 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
44 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
45 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
45 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
48 <td style="white-space: nowrap;">
48 <td style="white-space: nowrap;">
49 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
49 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
50 %if h.HasPermissionAny('hg.admin')():
50 %if h.HasPermissionAny('hg.admin')():
51 <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">${g2p.users_group.users_group_name}</a>
51 <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">${g2p.users_group.users_group_name}</a>
52 %else:
52 %else:
53 ${g2p.users_group.users_group_name}
53 ${g2p.users_group.users_group_name}
54 %endif
54 %endif
55 </td>
55 </td>
56 <td>
56 <td>
57 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
57 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
58 ${_('revoke')}
58 ${_('revoke')}
59 </span>
59 </span>
60 </td>
60 </td>
61 </tr>
61 </tr>
62 %endfor
62 %endfor
63 <tr id="add_perm_input">
63 <%
64 <td>${h.radio('perm_new_member','repository.none')}</td>
64 _tmpl = h.literal("""' \
65 <td>${h.radio('perm_new_member','repository.read')}</td>
65 <td><input type="radio" value="repository.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
66 <td>${h.radio('perm_new_member','repository.write')}</td>
66 <td><input type="radio" value="repository.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
67 <td>${h.radio('perm_new_member','repository.admin')}</td>
67 <td><input type="radio" value="repository.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
68 <td class='ac'>
68 <td><input type="radio" value="repository.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
69 <div class="perm_ac" id="perm_ac">
69 <td class="ac"> \
70 ${h.text('perm_new_member_name',class_='yui-ac-input')}
70 <div class="perm_ac" id="perm_ac_{0}"> \
71 ${h.hidden('perm_new_member_type')}
71 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
72 <div id="perm_container"></div>
72 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
73 </div>
73 <div id="perm_container_{0}"></div> \
74 </td>
74 </div> \
75 <td></td>
75 </td> \
76 </tr>
76 <td></td>'""")
77 %>
78 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
79 <tr class="new_members last_new_member" id="add_perm_input"></tr>
77 <tr>
80 <tr>
78 <td colspan="6">
81 <td colspan="6">
79 <span id="add_perm" class="add_icon" style="cursor: pointer;">
82 <span id="add_perm" class="add_icon" style="cursor: pointer;">
80 ${_('Add another member')}
83 ${_('Add another member')}
81 </span>
84 </span>
82 </td>
85 </td>
83 </tr>
86 </tr>
84 </table>
87 </table>
85 <script type="text/javascript">
88 <script type="text/javascript">
86 function ajaxActionUser(user_id, field_id) {
89 function ajaxActionUser(user_id, field_id) {
87 var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
90 var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
88 var callback = {
91 var callback = {
89 success: function (o) {
92 success: function (o) {
90 var tr = YUD.get(String(field_id));
93 var tr = YUD.get(String(field_id));
91 tr.parentNode.removeChild(tr);
94 tr.parentNode.removeChild(tr);
92 },
95 },
93 failure: function (o) {
96 failure: function (o) {
94 alert("${_('Failed to remove user')}");
97 alert("${_('Failed to remove user')}");
95 },
98 },
96 };
99 };
97 var postData = '_method=delete&user_id=' + user_id;
100 var postData = '_method=delete&user_id=' + user_id;
98 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
101 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
99 };
102 };
100
103
101 function ajaxActionUsersGroup(users_group_id,field_id){
104 function ajaxActionUsersGroup(users_group_id,field_id){
102 var sUrl = "${h.url('delete_repo_users_group',repo_name=c.repo_name)}";
105 var sUrl = "${h.url('delete_repo_users_group',repo_name=c.repo_name)}";
103 var callback = {
106 var callback = {
104 success:function(o){
107 success:function(o){
105 var tr = YUD.get(String(field_id));
108 var tr = YUD.get(String(field_id));
106 tr.parentNode.removeChild(tr);
109 tr.parentNode.removeChild(tr);
107 },
110 },
108 failure:function(o){
111 failure:function(o){
109 alert("${_('Failed to remove users group')}");
112 alert("${_('Failed to remove users group')}");
110 },
113 },
111 };
114 };
112 var postData = '_method=delete&users_group_id='+users_group_id;
115 var postData = '_method=delete&users_group_id='+users_group_id;
113 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
116 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
114 };
117 };
115
118
116 YUE.onDOMReady(function () {
119 YUE.onDOMReady(function () {
117 if (!YUD.hasClass('perm_new_member_name', 'error')) {
120 if (!YUD.hasClass('perm_new_member_name', 'error')) {
118 YUD.setStyle('add_perm_input', 'display', 'none');
121 YUD.setStyle('add_perm_input', 'display', 'none');
119 }
122 }
120 YAHOO.util.Event.addListener('add_perm', 'click', function () {
123 YAHOO.util.Event.addListener('add_perm', 'click', function () {
121 YUD.setStyle('add_perm_input', 'display', '');
124 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
122 YUD.setStyle('add_perm', 'opacity', '0.6');
123 YUD.setStyle('add_perm', 'cursor', 'default');
124 });
125 });
125 MembersAutoComplete(${c.users_array|n}, ${c.users_groups_array|n});
126 });
126 });
127
127
128 </script>
128 </script>
@@ -1,112 +1,112 b''
1 <table id="permissions_manage" class="noborder">
1 <table id="permissions_manage" class="noborder">
2 <tr>
2 <tr>
3 <td>${_('none')}</td>
3 <td>${_('none')}</td>
4 <td>${_('read')}</td>
4 <td>${_('read')}</td>
5 <td>${_('write')}</td>
5 <td>${_('write')}</td>
6 <td>${_('admin')}</td>
6 <td>${_('admin')}</td>
7 <td>${_('member')}</td>
7 <td>${_('member')}</td>
8 <td></td>
8 <td></td>
9 </tr>
9 </tr>
10 ## USERS
10 ## USERS
11 %for r2p in c.repos_group.repo_group_to_perm:
11 %for r2p in c.repos_group.repo_group_to_perm:
12 <tr id="id${id(r2p.user.username)}">
12 <tr id="id${id(r2p.user.username)}">
13 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
13 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
14 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
14 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
17 <td style="white-space: nowrap;">
17 <td style="white-space: nowrap;">
18 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
18 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
19 </td>
19 </td>
20 <td>
20 <td>
21 %if r2p.user.username !='default':
21 %if r2p.user.username !='default':
22 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
22 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
23 ${_('revoke')}
23 ${_('revoke')}
24 </span>
24 </span>
25 %endif
25 %endif
26 </td>
26 </td>
27 </tr>
27 </tr>
28 %endfor
28 %endfor
29
29
30 ## USERS GROUPS
30 ## USERS GROUPS
31 %for g2p in c.repos_group.users_group_to_perm:
31 %for g2p in c.repos_group.users_group_to_perm:
32 <tr id="id${id(g2p.users_group.users_group_name)}">
32 <tr id="id${id(g2p.users_group.users_group_name)}">
33 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
33 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
34 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
34 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
35 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
35 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
36 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
36 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
37 <td style="white-space: nowrap;">
37 <td style="white-space: nowrap;">
38 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
38 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
39 </td>
39 </td>
40 <td>
40 <td>
41 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
41 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
42 ${_('revoke')}
42 ${_('revoke')}
43 </span>
43 </span>
44 </td>
44 </td>
45 </tr>
45 </tr>
46 %endfor
46 %endfor
47 <tr id="add_perm_input">
47 <%
48 <td>${h.radio('perm_new_member','group.none')}</td>
48 _tmpl = h.literal("""' \
49 <td>${h.radio('perm_new_member','group.read')}</td>
49 <td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
50 <td>${h.radio('perm_new_member','group.write')}</td>
50 <td><input type="radio" value="group.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
51 <td>${h.radio('perm_new_member','group.admin')}</td>
51 <td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
52 <td class='ac'>
52 <td><input type="radio" value="group.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
53 <div class="perm_ac" id="perm_ac">
53 <td class="ac"> \
54 ${h.text('perm_new_member_name',class_='yui-ac-input')}
54 <div class="perm_ac" id="perm_ac_{0}"> \
55 ${h.hidden('perm_new_member_type')}
55 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
56 <div id="perm_container"></div>
56 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
57 </div>
57 <div id="perm_container_{0}"></div> \
58 </td>
58 </div> \
59 <td></td>
59 </td> \
60 </tr>
60 <td></td>'""")
61 %>
62 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
63 <tr class="new_members last_new_member" id="add_perm_input"></tr>
61 <tr>
64 <tr>
62 <td colspan="6">
65 <td colspan="6">
63 <span id="add_perm" class="add_icon" style="cursor: pointer;">
66 <span id="add_perm" class="add_icon" style="cursor: pointer;">
64 ${_('Add another member')}
67 ${_('Add another member')}
65 </span>
68 </span>
66 </td>
69 </td>
67 </tr>
70 </tr>
68 </table>
71 </table>
69 <script type="text/javascript">
72 <script type="text/javascript">
70 function ajaxActionUser(user_id, field_id) {
73 function ajaxActionUser(user_id, field_id) {
71 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.group_name)}";
74 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.group_name)}";
72 var callback = {
75 var callback = {
73 success: function (o) {
76 success: function (o) {
74 var tr = YUD.get(String(field_id));
77 var tr = YUD.get(String(field_id));
75 tr.parentNode.removeChild(tr);
78 tr.parentNode.removeChild(tr);
76 },
79 },
77 failure: function (o) {
80 failure: function (o) {
78 alert("${_('Failed to remove user')}");
81 alert("${_('Failed to remove user')}");
79 },
82 },
80 };
83 };
81 var postData = '_method=delete&user_id=' + user_id;
84 var postData = '_method=delete&user_id=' + user_id;
82 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
85 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
83 };
86 };
84
87
85 function ajaxActionUsersGroup(users_group_id,field_id){
88 function ajaxActionUsersGroup(users_group_id,field_id){
86 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.group_name)}";
89 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.group_name)}";
87 var callback = {
90 var callback = {
88 success:function(o){
91 success:function(o){
89 var tr = YUD.get(String(field_id));
92 var tr = YUD.get(String(field_id));
90 tr.parentNode.removeChild(tr);
93 tr.parentNode.removeChild(tr);
91 },
94 },
92 failure:function(o){
95 failure:function(o){
93 alert("${_('Failed to remove users group')}");
96 alert("${_('Failed to remove users group')}");
94 },
97 },
95 };
98 };
96 var postData = '_method=delete&users_group_id='+users_group_id;
99 var postData = '_method=delete&users_group_id='+users_group_id;
97 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
100 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
98 };
101 };
99
102
100 YUE.onDOMReady(function () {
103 YUE.onDOMReady(function () {
101 if (!YUD.hasClass('perm_new_member_name', 'error')) {
104 if (!YUD.hasClass('perm_new_member_name', 'error')) {
102 YUD.setStyle('add_perm_input', 'display', 'none');
105 YUD.setStyle('add_perm_input', 'display', 'none');
103 }
106 }
104 YAHOO.util.Event.addListener('add_perm', 'click', function () {
107 YAHOO.util.Event.addListener('add_perm', 'click', function () {
105 YUD.setStyle('add_perm_input', 'display', '');
108 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
106 YUD.setStyle('add_perm', 'opacity', '0.6');
107 YUD.setStyle('add_perm', 'cursor', 'default');
108 });
109 });
109 MembersAutoComplete(${c.users_array|n}, ${c.users_groups_array|n});
110 });
110 });
111
111
112 </script>
112 </script>
General Comments 0
You need to be logged in to leave comments. Login now