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