##// END OF EJS Templates
comments: multiple changes on comments navigation/display logic...
milka -
r4543:624997f0 default
parent child Browse files
Show More
@@ -252,7 +252,7 b' But please check this code'
252 252 var comment = $('#comment-'+commentId);
253 253 var commentData = comment.data();
254 254 if (commentData.commentInline) {
255 this.createComment(comment, commentId)
255 this.createComment(comment, f_path, line_no, commentId)
256 256 } else {
257 257 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
258 258 }
@@ -383,6 +383,9 b' class RepoCommitsView(RepoAppView):'
383 383 text = self.request.POST.get('text')
384 384 comment_type = self.request.POST.get('comment_type')
385 385 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
386 f_path = self.request.POST.get('f_path')
387 line_no = self.request.POST.get('line')
388 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
386 389
387 390 if status:
388 391 text = text or (_('Status change %(transition_icon)s %(status)s')
@@ -404,8 +407,8 b' class RepoCommitsView(RepoAppView):'
404 407 repo=self.db_repo.repo_id,
405 408 user=self._rhodecode_db_user.user_id,
406 409 commit_id=current_id,
407 f_path=self.request.POST.get('f_path'),
408 line_no=self.request.POST.get('line'),
410 f_path=f_path,
411 line_no=line_no,
409 412 status_change=(ChangesetStatus.get_status_lbl(status)
410 413 if status else None),
411 414 status_change_type=status,
@@ -447,19 +450,20 b' class RepoCommitsView(RepoAppView):'
447 450 # finalize, commit and redirect
448 451 Session().commit()
449 452
450 data = {
451 'target_id': h.safeid(h.safe_unicode(
452 self.request.POST.get('f_path'))),
453 }
453 data = {}
454 454 if comment:
455 comment_id = comment.comment_id
456 data[comment_id] = {
457 'target_id': target_elem_id
458 }
455 459 c.co = comment
456 460 c.at_version_num = 0
457 461 rendered_comment = render(
458 462 'rhodecode:templates/changeset/changeset_comment_block.mako',
459 463 self._get_template_context(c), self.request)
460 464
461 data.update(comment.get_dict())
462 data.update({'rendered_text': rendered_comment})
465 data[comment_id].update(comment.get_dict())
466 data[comment_id].update({'rendered_text': rendered_comment})
463 467
464 468 comment_broadcast_channel = channelstream.comment_channel(
465 469 self.db_repo_name, commit_obj=commit)
@@ -983,8 +983,9 b' class RepoPullRequestsView(RepoAppView, '
983 983 }
984 984 return data
985 985
986 def _get_existing_ids(self, post_data):
987 return filter(lambda e: e, map(safe_int, aslist(post_data.get('comments'), ',')))
986 @classmethod
987 def get_comment_ids(cls, post_data):
988 return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ',')))
988 989
989 990 @LoginRequired()
990 991 @NotAnonymous()
@@ -1022,7 +1023,7 b' class RepoPullRequestsView(RepoAppView, '
1022 1023 self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
1023 1024 all_comments = c.inline_comments_flat + c.comments
1024 1025
1025 existing_ids = self._get_existing_ids(self.request.POST)
1026 existing_ids = self.get_comment_ids(self.request.POST)
1026 1027 return _render('comments_table', all_comments, len(all_comments),
1027 1028 existing_ids=existing_ids)
1028 1029
@@ -1064,7 +1065,7 b' class RepoPullRequestsView(RepoAppView, '
1064 1065 .get_pull_request_resolved_todos(pull_request, include_drafts=False)
1065 1066
1066 1067 all_comments = c.unresolved_comments + c.resolved_comments
1067 existing_ids = self._get_existing_ids(self.request.POST)
1068 existing_ids = self.get_comment_ids(self.request.POST)
1068 1069 return _render('comments_table', all_comments, len(c.unresolved_comments),
1069 1070 todo_comments=True, existing_ids=existing_ids)
1070 1071
@@ -1518,6 +1519,150 b' class RepoPullRequestsView(RepoAppView, '
1518 1519 self._rhodecode_user)
1519 1520 raise HTTPNotFound()
1520 1521
1522 def _pull_request_comments_create(self, pull_request, comments):
1523 _ = self.request.translate
1524 data = {}
1525 pull_request_id = pull_request.pull_request_id
1526 if not comments:
1527 return
1528
1529 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
1530
1531 for entry in comments:
1532 c = self.load_default_context()
1533 comment_type = entry['comment_type']
1534 text = entry['text']
1535 status = entry['status']
1536 is_draft = str2bool(entry['is_draft'])
1537 resolves_comment_id = entry['resolves_comment_id']
1538 close_pull_request = entry['close_pull_request']
1539 f_path = entry['f_path']
1540 line_no = entry['line']
1541 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
1542
1543 # the logic here should work like following, if we submit close
1544 # pr comment, use `close_pull_request_with_comment` function
1545 # else handle regular comment logic
1546
1547 if close_pull_request:
1548 # only owner or admin or person with write permissions
1549 allowed_to_close = PullRequestModel().check_user_update(
1550 pull_request, self._rhodecode_user)
1551 if not allowed_to_close:
1552 log.debug('comment: forbidden because not allowed to close '
1553 'pull request %s', pull_request_id)
1554 raise HTTPForbidden()
1555
1556 # This also triggers `review_status_change`
1557 comment, status = PullRequestModel().close_pull_request_with_comment(
1558 pull_request, self._rhodecode_user, self.db_repo, message=text,
1559 auth_user=self._rhodecode_user)
1560 Session().flush()
1561 is_inline = comment.is_inline
1562
1563 PullRequestModel().trigger_pull_request_hook(
1564 pull_request, self._rhodecode_user, 'comment',
1565 data={'comment': comment})
1566
1567 else:
1568 # regular comment case, could be inline, or one with status.
1569 # for that one we check also permissions
1570 # Additionally ENSURE if somehow draft is sent we're then unable to change status
1571 allowed_to_change_status = PullRequestModel().check_user_change_status(
1572 pull_request, self._rhodecode_user) and not is_draft
1573
1574 if status and allowed_to_change_status:
1575 message = (_('Status change %(transition_icon)s %(status)s')
1576 % {'transition_icon': '>',
1577 'status': ChangesetStatus.get_status_lbl(status)})
1578 text = text or message
1579
1580 comment = CommentsModel().create(
1581 text=text,
1582 repo=self.db_repo.repo_id,
1583 user=self._rhodecode_user.user_id,
1584 pull_request=pull_request,
1585 f_path=f_path,
1586 line_no=line_no,
1587 status_change=(ChangesetStatus.get_status_lbl(status)
1588 if status and allowed_to_change_status else None),
1589 status_change_type=(status
1590 if status and allowed_to_change_status else None),
1591 comment_type=comment_type,
1592 is_draft=is_draft,
1593 resolves_comment_id=resolves_comment_id,
1594 auth_user=self._rhodecode_user,
1595 send_email=not is_draft, # skip notification for draft comments
1596 )
1597 is_inline = comment.is_inline
1598
1599 if allowed_to_change_status:
1600 # calculate old status before we change it
1601 old_calculated_status = pull_request.calculated_review_status()
1602
1603 # get status if set !
1604 if status:
1605 ChangesetStatusModel().set_status(
1606 self.db_repo.repo_id,
1607 status,
1608 self._rhodecode_user.user_id,
1609 comment,
1610 pull_request=pull_request
1611 )
1612
1613 Session().flush()
1614 # this is somehow required to get access to some relationship
1615 # loaded on comment
1616 Session().refresh(comment)
1617
1618 PullRequestModel().trigger_pull_request_hook(
1619 pull_request, self._rhodecode_user, 'comment',
1620 data={'comment': comment})
1621
1622 # we now calculate the status of pull request, and based on that
1623 # calculation we set the commits status
1624 calculated_status = pull_request.calculated_review_status()
1625 if old_calculated_status != calculated_status:
1626 PullRequestModel().trigger_pull_request_hook(
1627 pull_request, self._rhodecode_user, 'review_status_change',
1628 data={'status': calculated_status})
1629
1630 comment_id = comment.comment_id
1631 data[comment_id] = {
1632 'target_id': target_elem_id
1633 }
1634 Session().flush()
1635
1636 c.co = comment
1637 c.at_version_num = None
1638 c.is_new = True
1639 rendered_comment = render(
1640 'rhodecode:templates/changeset/changeset_comment_block.mako',
1641 self._get_template_context(c), self.request)
1642
1643 data[comment_id].update(comment.get_dict())
1644 data[comment_id].update({'rendered_text': rendered_comment})
1645
1646 Session().commit()
1647
1648 # skip channelstream for draft comments
1649 if all_drafts:
1650 comment_broadcast_channel = channelstream.comment_channel(
1651 self.db_repo_name, pull_request_obj=pull_request)
1652
1653 comment_data = data
1654 comment_type = 'inline' if is_inline else 'general'
1655 if len(data) == 1:
1656 msg = _('posted {} new {} comment').format(len(data), comment_type)
1657 else:
1658 msg = _('posted {} new {} comments').format(len(data), comment_type)
1659
1660 channelstream.comment_channelstream_push(
1661 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
1662 comment_data=comment_data)
1663
1664 return data
1665
1521 1666 @LoginRequired()
1522 1667 @NotAnonymous()
1523 1668 @HasRepoPermissionAnyDecorator(
@@ -1529,9 +1674,7 b' class RepoPullRequestsView(RepoAppView, '
1529 1674 def pull_request_comment_create(self):
1530 1675 _ = self.request.translate
1531 1676
1532 pull_request = PullRequest.get_or_404(
1533 self.request.matchdict['pull_request_id'])
1534 pull_request_id = pull_request.pull_request_id
1677 pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id'])
1535 1678
1536 1679 if pull_request.is_closed():
1537 1680 log.debug('comment: forbidden because pull request is closed')
@@ -1543,130 +1686,17 b' class RepoPullRequestsView(RepoAppView, '
1543 1686 log.debug('comment: forbidden because pull request is from forbidden repo')
1544 1687 raise HTTPForbidden()
1545 1688
1546 c = self.load_default_context()
1547
1548 status = self.request.POST.get('changeset_status', None)
1549 text = self.request.POST.get('text')
1550 comment_type = self.request.POST.get('comment_type')
1551 is_draft = str2bool(self.request.POST.get('draft'))
1552 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1553 close_pull_request = self.request.POST.get('close_pull_request')
1554
1555 # the logic here should work like following, if we submit close
1556 # pr comment, use `close_pull_request_with_comment` function
1557 # else handle regular comment logic
1558
1559 if close_pull_request:
1560 # only owner or admin or person with write permissions
1561 allowed_to_close = PullRequestModel().check_user_update(
1562 pull_request, self._rhodecode_user)
1563 if not allowed_to_close:
1564 log.debug('comment: forbidden because not allowed to close '
1565 'pull request %s', pull_request_id)
1566 raise HTTPForbidden()
1567
1568 # This also triggers `review_status_change`
1569 comment, status = PullRequestModel().close_pull_request_with_comment(
1570 pull_request, self._rhodecode_user, self.db_repo, message=text,
1571 auth_user=self._rhodecode_user)
1572 Session().flush()
1573 is_inline = comment.is_inline
1574
1575 PullRequestModel().trigger_pull_request_hook(
1576 pull_request, self._rhodecode_user, 'comment',
1577 data={'comment': comment})
1578
1579 else:
1580 # regular comment case, could be inline, or one with status.
1581 # for that one we check also permissions
1582 # Additionally ENSURE if somehow draft is sent we're then unable to change status
1583 allowed_to_change_status = PullRequestModel().check_user_change_status(
1584 pull_request, self._rhodecode_user) and not is_draft
1585
1586 if status and allowed_to_change_status:
1587 message = (_('Status change %(transition_icon)s %(status)s')
1588 % {'transition_icon': '>',
1589 'status': ChangesetStatus.get_status_lbl(status)})
1590 text = text or message
1591
1592 comment = CommentsModel().create(
1593 text=text,
1594 repo=self.db_repo.repo_id,
1595 user=self._rhodecode_user.user_id,
1596 pull_request=pull_request,
1597 f_path=self.request.POST.get('f_path'),
1598 line_no=self.request.POST.get('line'),
1599 status_change=(ChangesetStatus.get_status_lbl(status)
1600 if status and allowed_to_change_status else None),
1601 status_change_type=(status
1602 if status and allowed_to_change_status else None),
1603 comment_type=comment_type,
1604 is_draft=is_draft,
1605 resolves_comment_id=resolves_comment_id,
1606 auth_user=self._rhodecode_user,
1607 send_email=not is_draft, # skip notification for draft comments
1608 )
1609 is_inline = comment.is_inline
1610
1611 if allowed_to_change_status:
1612 # calculate old status before we change it
1613 old_calculated_status = pull_request.calculated_review_status()
1614
1615 # get status if set !
1616 if status:
1617 ChangesetStatusModel().set_status(
1618 self.db_repo.repo_id,
1619 status,
1620 self._rhodecode_user.user_id,
1621 comment,
1622 pull_request=pull_request
1623 )
1624
1625 Session().flush()
1626 # this is somehow required to get access to some relationship
1627 # loaded on comment
1628 Session().refresh(comment)
1629
1630 PullRequestModel().trigger_pull_request_hook(
1631 pull_request, self._rhodecode_user, 'comment',
1632 data={'comment': comment})
1633
1634 # we now calculate the status of pull request, and based on that
1635 # calculation we set the commits status
1636 calculated_status = pull_request.calculated_review_status()
1637 if old_calculated_status != calculated_status:
1638 PullRequestModel().trigger_pull_request_hook(
1639 pull_request, self._rhodecode_user, 'review_status_change',
1640 data={'status': calculated_status})
1641
1642 Session().commit()
1643
1644 data = {
1645 'target_id': h.safeid(h.safe_unicode(
1646 self.request.POST.get('f_path'))),
1689 comment_data = {
1690 'comment_type': self.request.POST.get('comment_type'),
1691 'text': self.request.POST.get('text'),
1692 'status': self.request.POST.get('changeset_status', None),
1693 'is_draft': self.request.POST.get('draft'),
1694 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
1695 'close_pull_request': self.request.POST.get('close_pull_request'),
1696 'f_path': self.request.POST.get('f_path'),
1697 'line': self.request.POST.get('line'),
1647 1698 }
1648
1649 if comment:
1650 c.co = comment
1651 c.at_version_num = None
1652 rendered_comment = render(
1653 'rhodecode:templates/changeset/changeset_comment_block.mako',
1654 self._get_template_context(c), self.request)
1655
1656 data.update(comment.get_dict())
1657 data.update({'rendered_text': rendered_comment})
1658
1659 # skip channelstream for draft comments
1660 if not is_draft:
1661 comment_broadcast_channel = channelstream.comment_channel(
1662 self.db_repo_name, pull_request_obj=pull_request)
1663
1664 comment_data = data
1665 comment_type = 'inline' if is_inline else 'general'
1666 channelstream.comment_channelstream_push(
1667 self.request, comment_broadcast_channel, self._rhodecode_user,
1668 _('posted a new {} comment').format(comment_type),
1669 comment_data=comment_data)
1699 data = self._pull_request_comments_create(pull_request, [comment_data])
1670 1700
1671 1701 return data
1672 1702
@@ -339,13 +339,12 b' def comment_channelstream_push(request, '
339 339
340 340 comment_data = kwargs.pop('comment_data', {})
341 341 user_data = kwargs.pop('user_data', {})
342 comment_id = comment_data.get('comment_id')
342 comment_id = comment_data.keys()[0] if comment_data else ''
343 343
344 message = '<strong>{}</strong> {} #{}, {}'.format(
344 message = '<strong>{}</strong> {} #{}'.format(
345 345 user.username,
346 346 msg,
347 347 comment_id,
348 _reload_link(_('Reload page to see new comments')),
349 348 )
350 349
351 350 message_obj = {
@@ -1148,7 +1148,7 b' class DiffLimitExceeded(Exception):'
1148 1148
1149 1149 # NOTE(marcink): if diffs.mako change, probably this
1150 1150 # needs a bump to next version
1151 CURRENT_DIFF_VERSION = 'v4'
1151 CURRENT_DIFF_VERSION = 'v5'
1152 1152
1153 1153
1154 1154 def _cleanup_cache_file(cached_diff_file):
@@ -370,7 +370,7 b' input[type="button"] {'
370 370 color: @alert2;
371 371
372 372 &:hover {
373 color: darken(@alert2,30%);
373 color: darken(@alert2, 30%);
374 374 }
375 375
376 376 &:disabled {
@@ -1002,7 +1002,7 b' input.filediff-collapse-state {'
1002 1002 .nav-chunk {
1003 1003 position: absolute;
1004 1004 right: 20px;
1005 margin-top: -17px;
1005 margin-top: -15px;
1006 1006 }
1007 1007
1008 1008 .nav-chunk.selected {
@@ -4,7 +4,7 b''
4 4
5 5
6 6 // Comments
7 @comment-outdated-opacity: 0.6;
7 @comment-outdated-opacity: 1.0;
8 8
9 9 .comments {
10 10 width: 100%;
@@ -64,32 +64,34 b' tr.inline-comments div {'
64 64 .comment-draft {
65 65 float: left;
66 66 margin-right: 10px;
67 font-weight: 600;
68 color: @alert3;
67 font-weight: 400;
68 color: @color-draft;
69 }
70
71 .comment-new {
72 float: left;
73 margin-right: 10px;
74 font-weight: 400;
75 color: @color-new;
69 76 }
70 77
71 78 .comment-label {
72 79 float: left;
73 80
74 padding: 0.4em 0.4em;
75 margin: 2px 4px 0px 0px;
76 display: inline-block;
81 padding: 0 8px 0 0;
77 82 min-height: 0;
78 83
79 84 text-align: center;
80 85 font-size: 10px;
81 line-height: .8em;
82 86
83 87 font-family: @text-italic;
84 88 font-style: italic;
85 89 background: #fff none;
86 90 color: @grey3;
87 border: 1px solid @grey4;
88 91 white-space: nowrap;
89 92
90 93 text-transform: uppercase;
91 94 min-width: 50px;
92 border-radius: 4px;
93 95
94 96 &.todo {
95 97 color: @color5;
@@ -277,64 +279,165 b' tr.inline-comments div {'
277 279 .comment-outdated {
278 280 opacity: @comment-outdated-opacity;
279 281 }
282
283 .comment-outdated-label {
284 color: @grey3;
285 padding-right: 4px;
286 }
280 287 }
281 288
282 289 .inline-comments {
283 border-radius: @border-radius;
290
284 291 .comment {
285 292 margin: 0;
286 border-radius: @border-radius;
287 293 }
294
288 295 .comment-outdated {
289 296 opacity: @comment-outdated-opacity;
290 297 }
291 298
299 .comment-outdated-label {
300 color: @grey3;
301 padding-right: 4px;
302 }
303
292 304 .comment-inline {
305
306 &:first-child {
307 margin: 4px 4px 0 4px;
308 border-top: 1px solid @grey5;
309 border-bottom: 0 solid @grey5;
310 border-left: 1px solid @grey5;
311 border-right: 1px solid @grey5;
312 .border-radius-top(4px);
313 }
314
315 &:only-child {
316 margin: 4px 4px 0 4px;
317 border-top: 1px solid @grey5;
318 border-bottom: 0 solid @grey5;
319 border-left: 1px solid @grey5;
320 border-right: 1px solid @grey5;
321 .border-radius-top(4px);
322 }
323
293 324 background: white;
294 325 padding: @comment-padding @comment-padding;
295 border: @comment-padding solid @grey6;
326 margin: 0 4px 0 4px;
327 border-top: 0 solid @grey5;
328 border-bottom: 0 solid @grey5;
329 border-left: 1px solid @grey5;
330 border-right: 1px solid @grey5;
296 331
297 332 .text {
298 333 border: none;
299 334 }
335
300 336 .meta {
301 337 border-bottom: 1px solid @grey6;
302 338 margin: -5px 0px;
303 339 line-height: 24px;
304 340 }
341
305 342 }
306 343 .comment-selected {
307 344 border-left: 6px solid @comment-highlight-color;
308 345 }
346
347 .comment-inline-form-open {
348 display: block !important;
349 }
350
309 351 .comment-inline-form {
310 padding: @comment-padding;
311 352 display: none;
312 353 }
313 .cb-comment-add-button {
314 margin: @comment-padding;
354
355 .comment-inline-form-edit {
356 padding: 0;
357 margin: 0px 4px 2px 4px;
358 }
359
360 .reply-thread-container {
361 display: table;
362 width: 100%;
363 padding: 0px 4px 4px 4px;
364 }
365
366 .reply-thread-container-wrapper {
367 margin: 0 4px 4px 4px;
368 border-top: 0 solid @grey5;
369 border-bottom: 1px solid @grey5;
370 border-left: 1px solid @grey5;
371 border-right: 1px solid @grey5;
372 .border-radius-bottom(4px);
373 }
374
375 .reply-thread-gravatar {
376 display: table-cell;
377 width: 24px;
378 height: 24px;
379 padding-top: 10px;
380 padding-left: 10px;
381 background-color: #eeeeee;
382 vertical-align: top;
315 383 }
316 /* hide add comment button when form is open */
384
385 .reply-thread-reply-button {
386 display: table-cell;
387 width: 100%;
388 height: 33px;
389 padding: 3px 8px;
390 margin-left: 8px;
391 background-color: #eeeeee;
392 }
393
394 .reply-thread-reply-button .cb-comment-add-button {
395 border-radius: 4px;
396 width: 100%;
397 padding: 6px 2px;
398 text-align: left;
399 cursor: text;
400 color: @grey3;
401 }
402 .reply-thread-reply-button .cb-comment-add-button:hover {
403 background-color: white;
404 color: @grey2;
405 }
406
407 .reply-thread-last {
408 display: table-cell;
409 width: 10px;
410 }
411
412 /* Hide reply box when it's a first element,
413 can happen when drafts are saved but not shown to specific user,
414 or there are outdated comments hidden
415 */
416 .reply-thread-container-wrapper:first-child:not(.comment-form-active) {
417 display: none;
418 }
419
420 .reply-thread-container-wrapper.comment-outdated {
421 display: none
422 }
423
424 /* hide add comment button when form is open */
317 425 .comment-inline-form-open ~ .cb-comment-add-button {
318 426 display: none;
319 427 }
320 .comment-inline-form-open {
321 display: block;
322 }
323 /* hide add comment button when form but no comments */
324 .comment-inline-form:first-child + .cb-comment-add-button {
325 display: none;
326 }
327 /* hide add comment button when no comments or form */
328 .cb-comment-add-button:first-child {
329 display: none;
330 }
428
331 429 /* hide add comment button when only comment is being deleted */
332 430 .comment-deleting:first-child + .cb-comment-add-button {
333 431 display: none;
334 432 }
433
434 /* hide add comment button when form but no comments */
435 .comment-inline-form:first-child + .cb-comment-add-button {
436 display: none;
437 }
438
335 439 }
336 440
337
338 441 .show-outdated-comments {
339 442 display: inline;
340 443 color: @rcblue;
@@ -387,23 +490,40 b' form.comment-form {'
387 490 }
388 491
389 492 .comment-footer {
390 position: relative;
493 display: table;
391 494 width: 100%;
392 min-height: 42px;
495 height: 42px;
393 496
394 .status_box,
497 .comment-status-box,
395 498 .cancel-button {
396 float: left;
397 499 display: inline-block;
398 500 }
399 501
400 .status_box {
502 .comment-status-box {
401 503 margin-left: 10px;
402 504 }
403 505
404 506 .action-buttons {
405 float: left;
406 display: inline-block;
507 display: table-cell;
508 padding: 5px 0 5px 2px;
509 }
510
511 .toolbar-text {
512 height: 42px;
513 display: table-cell;
514 vertical-align: bottom;
515 font-size: 11px;
516 color: @grey4;
517 text-align: right;
518
519 a {
520 color: @grey4;
521 }
522
523 p {
524 padding: 0;
525 margin: 0;
526 }
407 527 }
408 528
409 529 .action-buttons-extra {
@@ -434,10 +554,6 b' form.comment-form {'
434 554 margin-right: 0;
435 555 }
436 556
437 .comment-footer {
438 margin-bottom: 50px;
439 margin-top: 10px;
440 }
441 557 }
442 558
443 559
@@ -489,8 +605,8 b' form.comment-form {'
489 605 .injected_diff .comment-inline-form,
490 606 .comment-inline-form {
491 607 background-color: white;
492 margin-top: 10px;
493 margin-bottom: 20px;
608 margin-top: 4px;
609 margin-bottom: 10px;
494 610 }
495 611
496 612 .inline-form {
@@ -526,9 +642,6 b' form.comment-form {'
526 642 margin: 0px;
527 643 }
528 644
529 .comment-inline-form .comment-footer {
530 margin: 10px 0px 0px 0px;
531 }
532 645
533 646 .hide-inline-form-button {
534 647 margin-left: 5px;
@@ -554,6 +667,7 b' comment-area-text {'
554 667
555 668 .comment-area-header {
556 669 height: 35px;
670 border-bottom: 1px solid @grey5;
557 671 }
558 672
559 673 .comment-area-header .nav-links {
@@ -561,6 +675,7 b' comment-area-text {'
561 675 flex-flow: row wrap;
562 676 -webkit-flex-flow: row wrap;
563 677 width: 100%;
678 border: none;
564 679 }
565 680
566 681 .comment-area-footer {
@@ -629,14 +744,3 b' comment-area-text {'
629 744 border-bottom: 2px solid transparent;
630 745 }
631 746
632 .toolbar-text {
633 float: right;
634 font-size: 11px;
635 color: @grey4;
636 text-align: right;
637
638 a {
639 color: @grey4;
640 }
641 }
642
@@ -3212,7 +3212,12 b' details:not([open]) > :not(summary) {'
3212 3212
3213 3213 .sidebar-element {
3214 3214 margin-top: 20px;
3215 }
3215
3216 .icon-draft {
3217 color: @color-draft
3218 }
3219 }
3220
3216 3221
3217 3222 .right-sidebar-collapsed-state {
3218 3223 display: flex;
@@ -3235,5 +3240,4 b' details:not([open]) > :not(summary) {'
3235 3240
3236 3241 .old-comments-marker td {
3237 3242 padding-top: 15px;
3238 border-bottom: 1px solid @grey5;
3239 }
3243 }
@@ -47,6 +47,8 b''
47 47
48 48 // Highlight color for lines and colors
49 49 @comment-highlight-color: #ffd887;
50 @color-draft: darken(@alert3, 30%);
51 @color-new: darken(@alert1, 5%);
50 52
51 53 // FONTS
52 54 @basefontsize: 13px;
@@ -71,14 +71,20 b' export class RhodecodeApp extends Polyme'
71 71 if (elem) {
72 72 elem.handleNotification(data);
73 73 }
74
75 74 }
76 75
77 76 handleComment(data) {
78 if (data.message.comment_id) {
77
78 if (data.message.comment_data.length !== 0) {
79 79 if (window.refreshAllComments !== undefined) {
80 80 refreshAllComments()
81 81 }
82 var json_data = data.message.comment_data;
83
84 if (window.commentsController !== undefined) {
85
86 window.commentsController.attachComment(json_data)
87 }
82 88 }
83 89 }
84 90
@@ -364,12 +364,15 b' var _submitAjaxPOST = function(url, post'
364 364 postData['close_pull_request'] = true;
365 365 }
366 366
367 var submitSuccessCallback = function(o) {
367 // submitSuccess for general comments
368 var submitSuccessCallback = function(json_data) {
368 369 // reload page if we change status for single commit.
369 370 if (status && self.commitId) {
370 371 location.reload(true);
371 372 } else {
372 $('#injected_page_comments').append(o.rendered_text);
373 // inject newly created comments, json_data is {<comment_id>: {}}
374 self.attachGeneralComment(json_data)
375
373 376 self.resetCommentFormState();
374 377 timeagoActivate();
375 378 tooltipActivate();
@@ -565,26 +568,6 b' var CommentsController = function() {'
565 568 var mainComment = '#text';
566 569 var self = this;
567 570
568 this.cancelComment = function (node) {
569 var $node = $(node);
570 var edit = $(this).attr('edit');
571 if (edit) {
572 var $general_comments = null;
573 var $inline_comments = $node.closest('div.inline-comments');
574 if (!$inline_comments.length) {
575 $general_comments = $('#comments');
576 var $comment = $general_comments.parent().find('div.comment:hidden');
577 // show hidden general comment form
578 $('#cb-comment-general-form-placeholder').show();
579 } else {
580 var $comment = $inline_comments.find('div.comment:hidden');
581 }
582 $comment.show();
583 }
584 $node.closest('.comment-inline-form').remove();
585 return false;
586 };
587
588 571 this.showVersion = function (comment_id, comment_history_id) {
589 572
590 573 var historyViewUrl = pyroutes.url(
@@ -682,6 +665,35 b' var CommentsController = function() {'
682 665 return self.scrollToComment(node, -1, true);
683 666 };
684 667
668 this.cancelComment = function (node) {
669 var $node = $(node);
670 var edit = $(this).attr('edit');
671 var $inlineComments = $node.closest('div.inline-comments');
672
673 if (edit) {
674 var $general_comments = null;
675 if (!$inlineComments.length) {
676 $general_comments = $('#comments');
677 var $comment = $general_comments.parent().find('div.comment:hidden');
678 // show hidden general comment form
679 $('#cb-comment-general-form-placeholder').show();
680 } else {
681 var $comment = $inlineComments.find('div.comment:hidden');
682 }
683 $comment.show();
684 }
685 var $replyWrapper = $node.closest('.comment-inline-form').closest('.reply-thread-container-wrapper')
686 $replyWrapper.removeClass('comment-form-active');
687
688 var lastComment = $inlineComments.find('.comment-inline').last();
689 if ($(lastComment).hasClass('comment-outdated')) {
690 $replyWrapper.hide();
691 }
692
693 $node.closest('.comment-inline-form').remove();
694 return false;
695 };
696
685 697 this._deleteComment = function(node) {
686 698 var $node = $(node);
687 699 var $td = $node.closest('td');
@@ -751,7 +763,7 b' var CommentsController = function() {'
751 763 this.finalizeDrafts = function(commentIds) {
752 764
753 765 SwalNoAnimation.fire({
754 title: _ngettext('Submit {0} draft comment', 'Submit {0} draft comments', commentIds.length).format(commentIds.length),
766 title: _ngettext('Submit {0} draft comment.', 'Submit {0} draft comments.', commentIds.length).format(commentIds.length),
755 767 icon: 'warning',
756 768 showCancelButton: true,
757 769 confirmButtonText: _gettext('Yes, finalize drafts'),
@@ -764,6 +776,7 b' var CommentsController = function() {'
764 776 };
765 777
766 778 this.toggleWideMode = function (node) {
779
767 780 if ($('#content').hasClass('wrapper')) {
768 781 $('#content').removeClass("wrapper");
769 782 $('#content').addClass("wide-mode-wrapper");
@@ -778,16 +791,49 b' var CommentsController = function() {'
778 791
779 792 };
780 793
781 this.toggleComments = function(node, show) {
794 /**
795 * Turn off/on all comments in file diff
796 */
797 this.toggleDiffComments = function(node) {
798 // Find closes filediff container
782 799 var $filediff = $(node).closest('.filediff');
800 if ($(node).hasClass('toggle-on')) {
801 var show = false;
802 } else if ($(node).hasClass('toggle-off')) {
803 var show = true;
804 }
805
806 // Toggle each individual comment block, so we can un-toggle single ones
807 $.each($filediff.find('.toggle-comment-action'), function(idx, val) {
808 self.toggleLineComments($(val), show)
809 })
810
811 // since we change the height of the diff container that has anchor points for upper
812 // sticky header, we need to tell it to re-calculate those
813 if (window.updateSticky !== undefined) {
814 // potentially our comments change the active window size, so we
815 // notify sticky elements
816 updateSticky()
817 }
818
819 return false;
820 }
821
822 this.toggleLineComments = function(node, show) {
823
824 var trElem = $(node).closest('tr')
825
783 826 if (show === true) {
784 $filediff.removeClass('hide-comments');
827 // mark outdated comments as visible before the toggle;
828 $(trElem).find('.comment-outdated').show();
829 $(trElem).removeClass('hide-line-comments');
785 830 } else if (show === false) {
786 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
787 $filediff.addClass('hide-comments');
831 $(trElem).find('.comment-outdated').hide();
832 $(trElem).addClass('hide-line-comments');
788 833 } else {
789 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
790 $filediff.toggleClass('hide-comments');
834 // mark outdated comments as visible before the toggle;
835 $(trElem).find('.comment-outdated').show();
836 $(trElem).toggleClass('hide-line-comments');
791 837 }
792 838
793 839 // since we change the height of the diff container that has anchor points for upper
@@ -798,15 +844,6 b' var CommentsController = function() {'
798 844 updateSticky()
799 845 }
800 846
801 return false;
802 };
803
804 this.toggleLineComments = function(node) {
805 self.toggleComments(node, true);
806 var $node = $(node);
807 // mark outdated comments as visible before the toggle;
808 $(node.closest('tr')).find('.comment-outdated').show();
809 $node.closest('tr').toggleClass('hide-line-comments');
810 847 };
811 848
812 849 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
@@ -960,64 +997,58 b' var CommentsController = function() {'
960 997 return commentForm;
961 998 };
962 999
963 this.editComment = function(node) {
1000 this.editComment = function(node, line_no, f_path) {
1001 self.edit = true;
964 1002 var $node = $(node);
1003 var $td = $node.closest('td');
1004
965 1005 var $comment = $(node).closest('.comment');
966 1006 var comment_id = $($comment).data('commentId');
967 1007 var isDraft = $($comment).data('commentDraft');
968 var $form = null
1008 var $editForm = null
969 1009
970 1010 var $comments = $node.closest('div.inline-comments');
971 1011 var $general_comments = null;
972 var lineno = null;
973 1012
974 1013 if($comments.length){
975 1014 // inline comments setup
976 $form = $comments.find('.comment-inline-form');
977 lineno = self.getLineNumber(node)
1015 $editForm = $comments.find('.comment-inline-form');
1016 line_no = self.getLineNumber(node)
978 1017 }
979 1018 else{
980 1019 // general comments setup
981 1020 $comments = $('#comments');
982 $form = $comments.find('.comment-inline-form');
983 lineno = $comment[0].id
1021 $editForm = $comments.find('.comment-inline-form');
1022 line_no = $comment[0].id
984 1023 $('#cb-comment-general-form-placeholder').hide();
985 1024 }
986 1025
987 this.edit = true;
1026 if ($editForm.length === 0) {
988 1027
989 if (!$form.length) {
990
1028 // unhide all comments if they are hidden for a proper REPLY mode
991 1029 var $filediff = $node.closest('.filediff');
992 1030 $filediff.removeClass('hide-comments');
993 var f_path = $filediff.attr('data-f-path');
994
995 // create a new HTML from template
996 1031
997 var tmpl = $('#cb-comment-inline-form-template').html();
998 tmpl = tmpl.format(escapeHtml(f_path), lineno);
999 $form = $(tmpl);
1000 $comment.after($form)
1032 $editForm = self.createNewFormWrapper(f_path, line_no);
1033 if(f_path && line_no) {
1034 $editForm.addClass('comment-inline-form-edit')
1035 }
1001 1036
1002 var _form = $($form[0]).find('form');
1037 $comment.after($editForm)
1038
1039 var _form = $($editForm[0]).find('form');
1003 1040 var autocompleteActions = ['as_note',];
1004 1041 var commentForm = this.createCommentForm(
1005 _form, lineno, '', autocompleteActions, resolvesCommentId,
1042 _form, line_no, '', autocompleteActions, resolvesCommentId,
1006 1043 this.edit, comment_id);
1007 1044 var old_comment_text_binary = $comment.attr('data-comment-text');
1008 1045 var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
1009 1046 commentForm.cm.setValue(old_comment_text);
1010 1047 $comment.hide();
1048 tooltipActivate();
1011 1049
1012 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
1013 form: _form,
1014 parent: $comments,
1015 lineno: lineno,
1016 f_path: f_path}
1017 );
1018
1019 // set a CUSTOM submit handler for inline comments.
1020 commentForm.setHandleFormSubmit(function(o) {
1050 // set a CUSTOM submit handler for inline comment edit action.
1051 commentForm.setHandleFormSubmit(function(o) {
1021 1052 var text = commentForm.cm.getValue();
1022 1053 var commentType = commentForm.getCommentType();
1023 1054
@@ -1048,7 +1079,7 b' var CommentsController = function() {'
1048 1079 var postData = {
1049 1080 'text': text,
1050 1081 'f_path': f_path,
1051 'line': lineno,
1082 'line': line_no,
1052 1083 'comment_type': commentType,
1053 1084 'draft': isDraft,
1054 1085 'version': version,
@@ -1056,7 +1087,7 b' var CommentsController = function() {'
1056 1087 };
1057 1088
1058 1089 var submitSuccessCallback = function(json_data) {
1059 $form.remove();
1090 $editForm.remove();
1060 1091 $comment.show();
1061 1092 var postData = {
1062 1093 'text': text,
@@ -1121,8 +1152,7 b' var CommentsController = function() {'
1121 1152 'commit_id': templateContext.commit_data.commit_id});
1122 1153
1123 1154 _submitAjaxPOST(
1124 previewUrl, postData, successRenderCommit,
1125 failRenderCommit
1155 previewUrl, postData, successRenderCommit, failRenderCommit
1126 1156 );
1127 1157
1128 1158 try {
@@ -1178,49 +1208,103 b' var CommentsController = function() {'
1178 1208 });
1179 1209 }
1180 1210
1181 $form.addClass('comment-inline-form-open');
1211 $editForm.addClass('comment-inline-form-open');
1182 1212 };
1183 1213
1184 this.createComment = function(node, resolutionComment) {
1185 var resolvesCommentId = resolutionComment || null;
1214 this.attachComment = function(json_data) {
1215 var self = this;
1216 $.each(json_data, function(idx, val) {
1217 var json_data_elem = [val]
1218 var isInline = val.comment_f_path && val.comment_lineno
1219
1220 if (isInline) {
1221 self.attachInlineComment(json_data_elem)
1222 } else {
1223 self.attachGeneralComment(json_data_elem)
1224 }
1225 })
1226
1227 }
1228
1229 this.attachGeneralComment = function(json_data) {
1230 $.each(json_data, function(idx, val) {
1231 $('#injected_page_comments').append(val.rendered_text);
1232 })
1233 }
1234
1235 this.attachInlineComment = function(json_data) {
1236
1237 $.each(json_data, function (idx, val) {
1238 var line_qry = '*[data-line-no="{0}"]'.format(val.line_no);
1239 var html = val.rendered_text;
1240 var $inlineComments = $('#' + val.target_id)
1241 .find(line_qry)
1242 .find('.inline-comments');
1243
1244 var lastComment = $inlineComments.find('.comment-inline').last();
1245
1246 if (lastComment.length === 0) {
1247 // first comment, we append simply
1248 $inlineComments.find('.reply-thread-container-wrapper').before(html);
1249 } else {
1250 $(lastComment).after(html)
1251 }
1252
1253 })
1254
1255 };
1256
1257 this.createNewFormWrapper = function(f_path, line_no) {
1258 // create a new reply HTML form from template
1259 var tmpl = $('#cb-comment-inline-form-template').html();
1260 tmpl = tmpl.format(escapeHtml(f_path), line_no);
1261 return $(tmpl);
1262 }
1263
1264 this.createComment = function(node, f_path, line_no, resolutionComment) {
1265 self.edit = false;
1186 1266 var $node = $(node);
1187 1267 var $td = $node.closest('td');
1188 var $form = $td.find('.comment-inline-form');
1189 this.edit = false;
1268 var resolvesCommentId = resolutionComment || null;
1190 1269
1191 if (!$form.length) {
1270 var $replyForm = $td.find('.comment-inline-form');
1192 1271
1193 var $filediff = $node.closest('.filediff');
1194 $filediff.removeClass('hide-comments');
1195 var f_path = $filediff.attr('data-f-path');
1196 var lineno = self.getLineNumber(node);
1197 // create a new HTML from template
1198 var tmpl = $('#cb-comment-inline-form-template').html();
1199 tmpl = tmpl.format(escapeHtml(f_path), lineno);
1200 $form = $(tmpl);
1272 // if form isn't existing, we're generating a new one and injecting it.
1273 if ($replyForm.length === 0) {
1274
1275 // unhide/expand all comments if they are hidden for a proper REPLY mode
1276 self.toggleLineComments($node, true);
1277
1278 $replyForm = self.createNewFormWrapper(f_path, line_no);
1201 1279
1202 1280 var $comments = $td.find('.inline-comments');
1203 if (!$comments.length) {
1204 $comments = $(
1205 $('#cb-comments-inline-container-template').html());
1206 $td.append($comments);
1281
1282 // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
1283 if ($comments.length===0) {
1284 var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(f_path, line_no)
1285 var $reply_container = $('#cb-comments-inline-container-template')
1286 $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
1287 $td.append($($reply_container).html());
1207 1288 }
1208 1289
1209 $td.find('.cb-comment-add-button').before($form);
1290 // default comment button exists, so we prepend the form for leaving initial comment
1291 $td.find('.cb-comment-add-button').before($replyForm);
1292 // set marker, that we have a open form
1293 var $replyWrapper = $td.find('.reply-thread-container-wrapper')
1294 $replyWrapper.addClass('comment-form-active');
1210 1295
1211 var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
1212 var _form = $($form[0]).find('form');
1296 var lastComment = $comments.find('.comment-inline').last();
1297 if ($(lastComment).hasClass('comment-outdated')) {
1298 $replyWrapper.show();
1299 }
1300
1301 var _form = $($replyForm[0]).find('form');
1213 1302 var autocompleteActions = ['as_note', 'as_todo'];
1214 1303 var comment_id=null;
1215 var commentForm = this.createCommentForm(
1216 _form, lineno, placeholderText, autocompleteActions, resolvesCommentId, this.edit, comment_id);
1217
1218 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
1219 form: _form,
1220 parent: $td[0],
1221 lineno: lineno,
1222 f_path: f_path}
1223 );
1304 var placeholderText = _gettext('Leave a comment on file {0} line {1}.').format(f_path, line_no);
1305 var commentForm = self.createCommentForm(
1306 _form, line_no, placeholderText, autocompleteActions, resolvesCommentId,
1307 self.edit, comment_id);
1224 1308
1225 1309 // set a CUSTOM submit handler for inline comments.
1226 1310 commentForm.setHandleFormSubmit(function(o) {
@@ -1233,12 +1317,13 b' var CommentsController = function() {'
1233 1317 return;
1234 1318 }
1235 1319
1236 if (lineno === undefined) {
1237 alert('missing line !');
1320 if (line_no === undefined) {
1321 alert('Error: unable to fetch line number for this inline comment !');
1238 1322 return;
1239 1323 }
1324
1240 1325 if (f_path === undefined) {
1241 alert('missing file path !');
1326 alert('Error: unable to fetch file path for this inline comment !');
1242 1327 return;
1243 1328 }
1244 1329
@@ -1249,7 +1334,7 b' var CommentsController = function() {'
1249 1334 var postData = {
1250 1335 'text': text,
1251 1336 'f_path': f_path,
1252 'line': lineno,
1337 'line': line_no,
1253 1338 'comment_type': commentType,
1254 1339 'draft': isDraft,
1255 1340 'csrf_token': CSRF_TOKEN
@@ -1258,32 +1343,32 b' var CommentsController = function() {'
1258 1343 postData['resolves_comment_id'] = resolvesCommentId;
1259 1344 }
1260 1345
1346 // submitSuccess for inline commits
1261 1347 var submitSuccessCallback = function(json_data) {
1262 $form.remove();
1263 try {
1264 var html = json_data.rendered_text;
1265 var lineno = json_data.line_no;
1266 var target_id = json_data.target_id;
1348
1349 $replyForm.remove();
1350 $td.find('.reply-thread-container-wrapper').removeClass('comment-form-active');
1351
1352 try {
1353
1354 // inject newly created comments, json_data is {<comment_id>: {}}
1355 self.attachInlineComment(json_data)
1267 1356
1268 $comments.find('.cb-comment-add-button').before(html);
1357 //mark visually which comment was resolved
1358 if (resolvesCommentId) {
1359 commentForm.markCommentResolved(resolvesCommentId);
1360 }
1269 1361
1270 //mark visually which comment was resolved
1271 if (resolvesCommentId) {
1272 commentForm.markCommentResolved(resolvesCommentId);
1362 // run global callback on submit
1363 commentForm.globalSubmitSuccessCallback({
1364 draft: isDraft,
1365 comment_id: comment_id
1366 });
1367
1368 } catch (e) {
1369 console.error(e);
1273 1370 }
1274 1371
1275 // run global callback on submit
1276 commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
1277
1278 } catch (e) {
1279 console.error(e);
1280 }
1281
1282 // re trigger the linkification of next/prev navigation
1283 linkifyComments($('.inline-comment-injected'));
1284 timeagoActivate();
1285 tooltipActivate();
1286
1287 1372 if (window.updateSticky !== undefined) {
1288 1373 // potentially our comments change the active window size, so we
1289 1374 // notify sticky elements
@@ -1297,19 +1382,27 b' var CommentsController = function() {'
1297 1382
1298 1383 commentForm.setActionButtonsDisabled(false);
1299 1384
1385 // re trigger the linkification of next/prev navigation
1386 linkifyComments($('.inline-comment-injected'));
1387 timeagoActivate();
1388 tooltipActivate();
1300 1389 };
1390
1301 1391 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1302 1392 var prefix = "Error while submitting comment.\n"
1303 1393 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1304 1394 ajaxErrorSwal(message);
1305 1395 commentForm.resetCommentFormState(text)
1306 1396 };
1397
1307 1398 commentForm.submitAjaxPOST(
1308 1399 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1309 1400 });
1310 1401 }
1311 1402
1312 $form.addClass('comment-inline-form-open');
1403 // Finally "open" our reply form, since we know there are comments and we have the "attached" old form
1404 $replyForm.addClass('comment-inline-form-open');
1405 tooltipActivate();
1313 1406 };
1314 1407
1315 1408 this.createResolutionComment = function(commentId){
@@ -1319,9 +1412,12 b' var CommentsController = function() {'
1319 1412 var comment = $('#comment-'+commentId);
1320 1413 var commentData = comment.data();
1321 1414 if (commentData.commentInline) {
1322 this.createComment(comment, commentId)
1415 var f_path = commentData.fPath;
1416 var line_no = commentData.lineNo;
1417 //TODO check this if we need to give f_path/line_no
1418 this.createComment(comment, f_path, line_no, commentId)
1323 1419 } else {
1324 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
1420 this.createGeneralComment('general', "$placeholder", commentId)
1325 1421 }
1326 1422
1327 1423 return false;
@@ -1347,3 +1443,8 b' var CommentsController = function() {'
1347 1443 };
1348 1444
1349 1445 };
1446
1447 window.commentHelp = function(renderer) {
1448 var funcData = {'renderer': renderer}
1449 return renderTemplate('commentHelpHovercard', funcData)
1450 } No newline at end of file
@@ -42,12 +42,22 b' window.toggleElement = function (elem, t'
42 42 var $elem = $(elem);
43 43 var $target = $(target);
44 44
45 if ($target.is(':visible') || $target.length === 0) {
45 if (target !== undefined) {
46 var show = $target.is(':visible') || $target.length === 0;
47 } else {
48 var show = $elem.hasClass('toggle-off')
49 }
50
51 if (show) {
46 52 $target.hide();
47 53 $elem.html($elem.data('toggleOn'))
54 $elem.addClass('toggle-on')
55 $elem.removeClass('toggle-off')
48 56 } else {
49 57 $target.show();
50 58 $elem.html($elem.data('toggleOff'))
59 $elem.addClass('toggle-off')
60 $elem.removeClass('toggle-on')
51 61 }
52 62
53 63 return false
@@ -1,7 +1,5 b''
1 1 /__MAIN_APP__ - launched when rhodecode-app element is attached to DOM
2 2 /plugins/__REGISTER__ - launched after the onDomReady() code from rhodecode.js is executed
3 /ui/plugins/code/anchor_focus - launched when rc starts to scroll on load to anchor on PR/Codeview
4 /ui/plugins/code/comment_form_built - launched when injectInlineForm() is executed and the form object is created
5 3 /notifications - shows new event notifications
6 4 /connection_controller/subscribe - subscribes user to new channels
7 5 /connection_controller/presence - receives presence change messages
@@ -1,4 +1,4 b''
1 1 ## this is a dummy html file for partial rendering on server and sending
2 2 ## generated output via ajax after comment submit
3 3 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 ${comment.comment_block(c.co, inline=c.co.is_inline)}
4 ${comment.comment_block(c.co, inline=c.co.is_inline, is_new=c.is_new)}
@@ -10,7 +10,7 b''
10 10 %>
11 11
12 12
13 <%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
13 <%def name="comment_block(comment, inline=False, active_pattern_entries=None, is_new=False)">
14 14
15 15 <%
16 16 from rhodecode.model.comment import CommentsModel
@@ -40,6 +40,7 b''
40 40 data-comment-draft=${h.json.dumps(comment.draft)}
41 41 data-comment-renderer="${comment.renderer}"
42 42 data-comment-text="${comment.text | html_filters.base64,n}"
43 data-comment-f-path="${comment.f_path}"
43 44 data-comment-line-no="${comment.line_no}"
44 45 data-comment-inline=${h.json.dumps(inline)}
45 46 style="${'display: none;' if outdated_at_ver else ''}">
@@ -47,8 +48,17 b''
47 48 <div class="meta">
48 49 <div class="comment-type-label">
49 50 % if comment.draft:
50 <div class="tooltip comment-draft" title="${_('Draft comments are only visible to the author until submitted')}.">DRAFT</div>
51 <div class="tooltip comment-draft" title="${_('Draft comments are only visible to the author until submitted')}.">
52 DRAFT
53 </div>
51 54 % endif
55
56 % if is_new:
57 <div class="tooltip comment-new" title="${_('This comment was added while you browsed this page')}.">
58 NEW
59 </div>
60 % endif
61
52 62 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
53 63
54 64 ## TODO COMMENT
@@ -176,7 +186,7 b''
176 186 % if inline:
177 187 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
178 188 % if outdated_at_ver:
179 <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">outdated ${'v{}'.format(comment_ver)}</code>
189 <strong class="comment-outdated-label">outdated</strong> <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code>
180 190 <code class="action-divider">|</code>
181 191 % elif comment_ver:
182 192 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code>
@@ -222,12 +232,13 b''
222 232 %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
223 233 <div class="dropdown-divider"></div>
224 234 <div class="dropdown-item">
225 <a onclick="return Rhodecode.comments.editComment(this);" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a>
235 <a onclick="return Rhodecode.comments.editComment(this, '${comment.line_no}', '${comment.f_path}');" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a>
226 236 </div>
227 237 <div class="dropdown-item">
228 238 <a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a>
229 239 </div>
230 % if comment.draft:
240 ## Only available in EE edition
241 % if comment.draft and c.rhodecode_edition_id == 'EE':
231 242 <div class="dropdown-item">
232 243 <a onclick="return Rhodecode.comments.finalizeDrafts([${comment.comment_id}]);" class="btn btn-link btn-sm finalize-draft-comment">${_('Submit draft')}</a>
233 244 </div>
@@ -391,7 +402,7 b''
391 402
392 403 <div class="comment-area-write" style="display: block;">
393 404 <div id="edit-container">
394 <div style="padding: 40px 0">
405 <div style="padding: 20px 0px 0px 0;">
395 406 ${_('You need to be logged in to leave comments.')}
396 407 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
397 408 </div>
@@ -450,7 +461,7 b''
450 461 </div>
451 462
452 463 <div class="comment-area-write" style="display: block;">
453 <div id="edit-container_${lineno_id}">
464 <div id="edit-container_${lineno_id}" style="margin-top: -1px">
454 465 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
455 466 </div>
456 467 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
@@ -500,40 +511,47 b''
500 511 <input class="btn btn-success comment-button-input submit-comment-action" id="save_${lineno_id}" name="save" type="submit" value="${_('Add comment')}" data-is-draft=false onclick="$(this).addClass('submitter')">
501 512
502 513 % if form_type == 'inline':
503 <input class="btn btn-warning comment-button-input submit-draft-action" id="save_draft_${lineno_id}" name="save_draft" type="submit" value="${_('Add draft')}" data-is-draft=true onclick="$(this).addClass('submitter')">
514 % if c.rhodecode_edition_id == 'EE':
515 ## Disable the button for CE, the "real" validation is in the backend code anyway
516 <input class="btn btn-warning comment-button-input submit-draft-action" id="save_draft_${lineno_id}" name="save_draft" type="submit" value="${_('Add draft')}" data-is-draft=true onclick="$(this).addClass('submitter')">
517 % else:
518 <input class="tooltip btn btn-warning comment-button-input submit-draft-action disabled" type="submit" disabled="disabled" value="${_('Add draft')}" onclick="return false;" title="Draft comments only available in EE edition of RhodeCode">
519 % endif
520 % endif
521
522 % if review_statuses:
523 <div class="comment-status-box">
524 <select id="change_status_${lineno_id}" name="changeset_status">
525 <option></option> ## Placeholder
526 % for status, lbl in review_statuses:
527 <option value="${status}" data-status="${status}">${lbl}</option>
528 %if is_pull_request and change_status and status in ('approved', 'rejected'):
529 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
530 %endif
531 % endfor
532 </select>
533 </div>
504 534 % endif
505 535
506 536 ## inline for has a file, and line-number together with cancel hide button.
507 537 % if form_type == 'inline':
508 538 <input type="hidden" name="f_path" value="{0}">
509 539 <input type="hidden" name="line" value="${lineno_id}">
510 <button type="button" class="tooltip cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);" title="Hide comment box">
540 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
511 541 <i class="icon-cancel-circled2"></i>
512 542 </button>
513 543 % endif
514 544 </div>
515 545
516 % if review_statuses:
517 <div class="status_box">
518 <select id="change_status_${lineno_id}" name="changeset_status">
519 <option></option> ## Placeholder
520 % for status, lbl in review_statuses:
521 <option value="${status}" data-status="${status}">${lbl}</option>
522 %if is_pull_request and change_status and status in ('approved', 'rejected'):
523 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
524 %endif
525 % endfor
526 </select>
527 </div>
528 % endif
529
530 546 <div class="toolbar-text">
531 547 <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %>
532 ${_('Comments parsed using {} syntax.').format(renderer_url)|n} <br/>
533 <span class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span>
534 ${_('and')}
535 <span class="tooltip" title="${_('Start typing with / for certain actions to be triggered via text box.')}">`/` autocomplete</span>
536 ${_('actions supported.')}
548 <p>${_('Styling with {} is supported.').format(renderer_url)|n}
549
550 <i class="icon-info-circled tooltip-hovercard"
551 data-hovercard-alt="ALT"
552 data-hovercard-url="javascript:commentHelp('${c.visual.default_renderer.upper()}')"
553 data-comment-json-b64='${h.b64(h.json.dumps({}))}'></i>
554 </p>
537 555 </div>
538 556 </div>
539 557
@@ -1,3 +1,4 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 2 <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/>
2 3
3 4 <%def name="diff_line_anchor(commit, filename, line, type)"><%
@@ -74,24 +75,9 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
74 75
75 76 <div class="js-template" id="cb-comment-inline-form-template">
76 77 <div class="comment-inline-form ac">
77
78 %if c.rhodecode_user.username != h.DEFAULT_USER:
78 %if not c.rhodecode_user.is_default:
79 79 ## render template for inline comments
80 80 ${commentblock.comment_form(form_type='inline')}
81 %else:
82 ${h.form('', class_='inline-form comment-form-login', method='get')}
83 <div class="pull-left">
84 <div class="comment-help pull-right">
85 ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
86 </div>
87 </div>
88 <div class="comment-button pull-right">
89 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
90 ${_('Cancel')}
91 </button>
92 </div>
93 <div class="clearfix"></div>
94 ${h.end_form()}
95 81 %endif
96 82 </div>
97 83 </div>
@@ -327,7 +313,7 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
327 313 </label>
328 314
329 315 ${diff_menu(filediff, use_comments=use_comments)}
330 <table data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
316 <table id="file-${h.safeid(h.safe_unicode(filediff.patch['filename']))}" data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
331 317
332 318 ## new/deleted/empty content case
333 319 % if not filediff.hunks:
@@ -626,8 +612,10 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
626 612
627 613 % if use_comments:
628 614 |
629 <a href="#" onclick="return Rhodecode.comments.toggleComments(this);">
630 <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
615 <a href="#" onclick="Rhodecode.comments.toggleDiffComments(this);return toggleElement(this)"
616 data-toggle-on="${_('Hide comments')}"
617 data-toggle-off="${_('Show comments')}">
618 <span class="hide-comment-button">${_('Hide comments')}</span>
631 619 </a>
632 620 % endif
633 621
@@ -637,23 +625,36 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
637 625 </%def>
638 626
639 627
640 <%def name="inline_comments_container(comments, active_pattern_entries=None)">
628 <%def name="inline_comments_container(comments, active_pattern_entries=None, line_no='', f_path='')">
641 629
642 630 <div class="inline-comments">
643 631 %for comment in comments:
644 632 ${commentblock.comment_block(comment, inline=True, active_pattern_entries=active_pattern_entries)}
645 633 %endfor
646 % if comments and comments[-1].outdated:
647 <span class="btn btn-secondary cb-comment-add-button comment-outdated}" style="display: none;}">
648 ${_('Add another comment')}
649 </span>
650 % else:
651 <span onclick="return Rhodecode.comments.createComment(this)" class="btn btn-secondary cb-comment-add-button">
652 ${_('Add another comment')}
653 </span>
654 % endif
634
635 <%
636 extra_class = ''
637 extra_style = ''
638
639 if comments and comments[-1].outdated:
640 extra_class = ' comment-outdated'
641 extra_style = 'display: none;'
655 642
643 %>
644 <div class="reply-thread-container-wrapper${extra_class}" style="${extra_style}">
645 <div class="reply-thread-container${extra_class}">
646 <div class="reply-thread-gravatar">
647 ${base.gravatar(c.rhodecode_user.email, 20, tooltip=True, user=c.rhodecode_user)}
648 </div>
649 <div class="reply-thread-reply-button">
650 ## initial reply button, some JS logic can append here a FORM to leave a first comment.
651 <button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, '${f_path}', '${line_no}', null)">Reply...</button>
652 </div>
653 <div class="reply-thread-last"></div>
654 </div>
655 </div>
656 656 </div>
657
657 658 </%def>
658 659
659 660 <%!
@@ -721,9 +722,9 b' def get_comments_for(diff_type, comments'
721 722 %endif
722 723 %if line_old_comments_no_drafts:
723 724 % if has_outdated:
724 <i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
725 <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
725 726 % else:
726 <i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
727 <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
727 728 % endif
728 729 %endif
729 730 </td>
@@ -737,16 +738,18 b' def get_comments_for(diff_type, comments'
737 738 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
738 739 %endif
739 740 </td>
741
742 <% line_no = 'o{}'.format(line.original.lineno) %>
740 743 <td class="cb-content ${action_class(line.original.action)}"
741 data-line-no="o${line.original.lineno}"
744 data-line-no="${line_no}"
742 745 >
743 746 %if use_comments and line.original.lineno:
744 ${render_add_comment_button()}
747 ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])}
745 748 %endif
746 749 <span class="cb-code"><span class="cb-action ${action_class(line.original.action)}"></span>${line.original.content or '' | n}</span>
747 750
748 751 %if use_comments and line.original.lineno and line_old_comments:
749 ${inline_comments_container(line_old_comments, active_pattern_entries=active_pattern_entries)}
752 ${inline_comments_container(line_old_comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])}
750 753 %endif
751 754
752 755 </td>
@@ -766,9 +769,9 b' def get_comments_for(diff_type, comments'
766 769
767 770 %if line_new_comments_no_drafts:
768 771 % if has_outdated:
769 <i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
772 <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
770 773 % else:
771 <i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
774 <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
772 775 % endif
773 776 %endif
774 777 </div>
@@ -783,22 +786,25 b' def get_comments_for(diff_type, comments'
783 786 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
784 787 %endif
785 788 </td>
789
790 <% line_no = 'n{}'.format(line.modified.lineno) %>
786 791 <td class="cb-content ${action_class(line.modified.action)}"
787 data-line-no="n${line.modified.lineno}"
792 data-line-no="${line_no}"
788 793 >
789 794 %if use_comments and line.modified.lineno:
790 ${render_add_comment_button()}
795 ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])}
791 796 %endif
792 797 <span class="cb-code"><span class="cb-action ${action_class(line.modified.action)}"></span>${line.modified.content or '' | n}</span>
793 %if use_comments and line.modified.lineno and line_new_comments:
794 ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries)}
795 %endif
796 798 % if line_action in ['+', '-'] and prev_line_action not in ['+', '-']:
797 799 <div class="nav-chunk" style="visibility: hidden">
798 800 <i class="icon-eye" title="viewing diff hunk-${hunk.index}-${chunk_count}"></i>
799 801 </div>
800 802 <% chunk_count +=1 %>
801 803 % endif
804 %if use_comments and line.modified.lineno and line_new_comments:
805 ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])}
806 %endif
807
802 808 </td>
803 809 </tr>
804 810 %endfor
@@ -830,9 +836,9 b' def get_comments_for(diff_type, comments'
830 836
831 837 % if comments_no_drafts:
832 838 % if has_outdated:
833 <i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
839 <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
834 840 % else:
835 <i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
841 <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
836 842 % endif
837 843 % endif
838 844 </div>
@@ -857,15 +863,16 b' def get_comments_for(diff_type, comments'
857 863 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
858 864 %endif
859 865 </td>
866 <% line_no = '{}{}'.format(new_line_no and 'n' or 'o', new_line_no or old_line_no) %>
860 867 <td class="cb-content ${action_class(action)}"
861 data-line-no="${(new_line_no and 'n' or 'o')}${(new_line_no or old_line_no)}"
868 data-line-no="${line_no}"
862 869 >
863 870 %if use_comments:
864 ${render_add_comment_button()}
871 ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])}
865 872 %endif
866 873 <span class="cb-code"><span class="cb-action ${action_class(action)}"></span> ${content or '' | n}</span>
867 874 %if use_comments and comments:
868 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
875 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])}
869 876 %endif
870 877 </td>
871 878 </tr>
@@ -886,10 +893,12 b' def get_comments_for(diff_type, comments'
886 893 </%def>file changes
887 894
888 895
889 <%def name="render_add_comment_button()">
890 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)">
896 <%def name="render_add_comment_button(line_no='', f_path='')">
897 % if not c.rhodecode_user.is_default:
898 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this, '${f_path}', '${line_no}', null)">
891 899 <span><i class="icon-comment"></i></span>
892 900 </button>
901 % endif
893 902 </%def>
894 903
895 904 <%def name="render_diffset_menu(diffset, range_diff_on=None, commit=None, pull_request_menu=None)">
@@ -456,7 +456,7 b''
456 456 </div>
457 457
458 458 <div class="markup-form-area-write" style="display: block;">
459 <div id="edit-container_${form_id}">
459 <div id="edit-container_${form_id}" style="margin-top: -1px">
460 460 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
461 461 </div>
462 462 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
@@ -237,6 +237,24 b' if (show_disabled) {'
237 237
238 238 </script>
239 239
240 <script id="ejs_commentHelpHovercard" type="text/template" class="ejsTemplate">
241
242 <div>
243 Use <strong>@username</strong> mention syntax to send direct notification to this RhodeCode user.<br/>
244 Typing / starts autocomplete for certain action, e.g set review status, or comment type. <br/>
245 <br/>
246 Use <strong>Cmd/ctrl+enter</strong> to submit comment, or <strong>Shift+Cmd/ctrl+enter</strong> to submit a draft.<br/>
247 <br/>
248 <strong>Draft comments</strong> are private to the author, and trigger no notification to others.<br/>
249 They are permanent until deleted, or converted to regular comments.<br/>
250 <br/>
251 <br/>
252 </div>
253
254 </script>
255
256
257
240 258 ##// END OF EJS Templates
241 259 </div>
242 260
@@ -846,6 +846,7 b' versionController.init();'
846 846
847 847 reviewersController = new ReviewersController();
848 848 commitsController = new CommitsController();
849 commentsController = new CommentsController();
849 850
850 851 updateController = new UpdatePrController();
851 852
@@ -1002,6 +1003,8 b' window.setObserversData = ${c.pull_reque'
1002 1003 alert('okok !' + commentIds)
1003 1004
1004 1005 }
1006 // register globally so inject comment logic can re-use it.
1007 window.commentsController = commentsController;
1005 1008
1006 1009 })
1007 1010 </script>
General Comments 0
You need to be logged in to leave comments. Login now