Show More
@@ -45,16 +45,17 b' from rhodecode.lib.auth import (' | |||
|
45 | 45 | from rhodecode.lib.channelstream import channelstream_request |
|
46 | 46 | from rhodecode.lib.compat import OrderedDict |
|
47 | 47 | from rhodecode.lib.utils import jsonify |
|
48 |
from rhodecode.lib.utils2 import |
|
|
48 | from rhodecode.lib.utils2 import ( | |
|
49 | safe_int, safe_str, str2bool, safe_unicode, UnsafeAttributeDict) | |
|
49 | 50 | from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason |
|
50 | 51 | from rhodecode.lib.vcs.exceptions import ( |
|
51 | 52 | EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError, |
|
52 | 53 | NodeDoesNotExistError) |
|
53 | from rhodecode.lib.diffs import LimitedDiffContainer | |
|
54 | ||
|
54 | 55 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
55 | 56 | from rhodecode.model.comment import ChangesetCommentsModel |
|
56 |
from rhodecode.model.db import PullRequest, ChangesetStatus, ChangesetComment, |
|
|
57 | Repository | |
|
57 | from rhodecode.model.db import (PullRequest, ChangesetStatus, ChangesetComment, | |
|
58 | Repository, PullRequestVersion) | |
|
58 | 59 | from rhodecode.model.forms import PullRequestForm |
|
59 | 60 | from rhodecode.model.meta import Session |
|
60 | 61 | from rhodecode.model.pull_request import PullRequestModel |
@@ -675,46 +676,133 b' class PullrequestsController(BaseRepoCon' | |||
|
675 | 676 | return redirect(url('my_account_pullrequests')) |
|
676 | 677 | raise HTTPForbidden() |
|
677 | 678 | |
|
679 | def _get_pr_version(self, pull_request_id, version=None): | |
|
680 | pull_request_id = safe_int(pull_request_id) | |
|
681 | at_version = None | |
|
682 | if version: | |
|
683 | pull_request_ver = PullRequestVersion.get_or_404(version) | |
|
684 | pull_request_obj = pull_request_ver | |
|
685 | _org_pull_request_obj = pull_request_ver.pull_request | |
|
686 | at_version = pull_request_ver.pull_request_version_id | |
|
687 | else: | |
|
688 | _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(pull_request_id) | |
|
689 | ||
|
690 | class PullRequestDisplay(object): | |
|
691 | """ | |
|
692 | Special object wrapper for showing PullRequest data via Versions | |
|
693 | It mimics PR object as close as possible. This is read only object | |
|
694 | just for display | |
|
695 | """ | |
|
696 | def __init__(self, attrs): | |
|
697 | self.attrs = attrs | |
|
698 | # internal have priority over the given ones via attrs | |
|
699 | self.internal = ['versions'] | |
|
700 | ||
|
701 | def __getattr__(self, item): | |
|
702 | if item in self.internal: | |
|
703 | return getattr(self, item) | |
|
704 | try: | |
|
705 | return self.attrs[item] | |
|
706 | except KeyError: | |
|
707 | raise AttributeError( | |
|
708 | '%s object has no attribute %s' % (self, item)) | |
|
709 | ||
|
710 | def versions(self): | |
|
711 | return pull_request_obj.versions.order_by( | |
|
712 | PullRequestVersion.pull_request_version_id).all() | |
|
713 | ||
|
714 | def is_closed(self): | |
|
715 | return pull_request_obj.is_closed() | |
|
716 | ||
|
717 | attrs = UnsafeAttributeDict(pull_request_obj.get_api_data()) | |
|
718 | ||
|
719 | attrs.author = UnsafeAttributeDict( | |
|
720 | pull_request_obj.author.get_api_data()) | |
|
721 | if pull_request_obj.target_repo: | |
|
722 | attrs.target_repo = UnsafeAttributeDict( | |
|
723 | pull_request_obj.target_repo.get_api_data()) | |
|
724 | attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url | |
|
725 | ||
|
726 | if pull_request_obj.source_repo: | |
|
727 | attrs.source_repo = UnsafeAttributeDict( | |
|
728 | pull_request_obj.source_repo.get_api_data()) | |
|
729 | attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url | |
|
730 | ||
|
731 | attrs.source_ref_parts = pull_request_obj.source_ref_parts | |
|
732 | attrs.target_ref_parts = pull_request_obj.target_ref_parts | |
|
733 | ||
|
734 | attrs.shadow_merge_ref = _org_pull_request_obj.shadow_merge_ref | |
|
735 | ||
|
736 | pull_request_ver = PullRequestDisplay(attrs) | |
|
737 | ||
|
738 | return _org_pull_request_obj, pull_request_obj, \ | |
|
739 | pull_request_ver, at_version | |
|
740 | ||
|
678 | 741 | @LoginRequired() |
|
679 | 742 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
680 | 743 | 'repository.admin') |
|
681 | 744 | def show(self, repo_name, pull_request_id): |
|
682 | 745 | pull_request_id = safe_int(pull_request_id) |
|
683 | c.pull_request = PullRequest.get_or_404(pull_request_id) | |
|
746 | ||
|
747 | version = request.GET.get('version') | |
|
748 | pull_request_latest, \ | |
|
749 | pull_request, \ | |
|
750 | pull_request_ver, \ | |
|
751 | at_version = self._get_pr_version(pull_request_id, version=version) | |
|
684 | 752 | |
|
685 | 753 | c.template_context['pull_request_data']['pull_request_id'] = \ |
|
686 | 754 | pull_request_id |
|
687 | 755 | |
|
688 | 756 | # pull_requests repo_name we opened it against |
|
689 | 757 | # ie. target_repo must match |
|
690 |
if repo_name != |
|
|
758 | if repo_name != pull_request.target_repo.repo_name: | |
|
691 | 759 | raise HTTPNotFound |
|
692 | 760 | |
|
693 | c.allowed_to_change_status = PullRequestModel(). \ | |
|
694 | check_user_change_status(c.pull_request, c.rhodecode_user) | |
|
695 | c.allowed_to_update = PullRequestModel().check_user_update( | |
|
696 | c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed() | |
|
697 | c.allowed_to_merge = PullRequestModel().check_user_merge( | |
|
698 | c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed() | |
|
699 | 761 | c.shadow_clone_url = PullRequestModel().get_shadow_clone_url( |
|
700 |
|
|
|
701 | c.allowed_to_delete = PullRequestModel().check_user_delete( | |
|
702 | c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed() | |
|
762 | pull_request) | |
|
763 | ||
|
764 | if at_version: | |
|
765 | c.allowed_to_change_status = False | |
|
766 | else: | |
|
767 | c.allowed_to_change_status = PullRequestModel(). \ | |
|
768 | check_user_change_status(pull_request, c.rhodecode_user) | |
|
769 | ||
|
770 | if at_version: | |
|
771 | c.allowed_to_update = False | |
|
772 | else: | |
|
773 | c.allowed_to_update = PullRequestModel().check_user_update( | |
|
774 | pull_request, c.rhodecode_user) and not pull_request.is_closed() | |
|
775 | ||
|
776 | if at_version: | |
|
777 | c.allowed_to_merge = False | |
|
778 | else: | |
|
779 | c.allowed_to_merge = PullRequestModel().check_user_merge( | |
|
780 | pull_request, c.rhodecode_user) and not pull_request.is_closed() | |
|
781 | ||
|
782 | if at_version: | |
|
783 | c.allowed_to_delete = False | |
|
784 | else: | |
|
785 | c.allowed_to_delete = PullRequestModel().check_user_delete( | |
|
786 | pull_request, c.rhodecode_user) and not pull_request.is_closed() | |
|
787 | ||
|
788 | if at_version: | |
|
789 | c.allowed_to_comment = False | |
|
790 | else: | |
|
791 | c.allowed_to_comment = not pull_request.is_closed() | |
|
703 | 792 | |
|
704 | 793 | cc_model = ChangesetCommentsModel() |
|
705 | 794 | |
|
706 |
c.pull_request_reviewers = |
|
|
795 | c.pull_request_reviewers = pull_request.reviewers_statuses() | |
|
707 | 796 | |
|
708 |
c.pull_request_review_status = |
|
|
797 | c.pull_request_review_status = pull_request.calculated_review_status() | |
|
709 | 798 | c.pr_merge_status, c.pr_merge_msg = PullRequestModel().merge_status( |
|
710 |
|
|
|
799 | pull_request) | |
|
711 | 800 | c.approval_msg = None |
|
712 | 801 | if c.pull_request_review_status != ChangesetStatus.STATUS_APPROVED: |
|
713 | 802 | c.approval_msg = _('Reviewer approval is pending.') |
|
714 | 803 | c.pr_merge_status = False |
|
715 | 804 | # load compare data into template context |
|
716 |
enable_comments = not |
|
|
717 | ||
|
805 | enable_comments = not pull_request.is_closed() | |
|
718 | 806 | |
|
719 | 807 | # inline comments |
|
720 | 808 | c.inline_comments = cc_model.get_inline_comments( |
@@ -725,23 +813,26 b' class PullrequestsController(BaseRepoCon' | |||
|
725 | 813 | c.inline_comments, version=at_version) |
|
726 | 814 | |
|
727 | 815 | self._load_compare_data( |
|
728 |
|
|
|
816 | pull_request, c.inline_comments, enable_comments=enable_comments) | |
|
729 | 817 | |
|
730 | 818 | # outdated comments |
|
731 | 819 | c.outdated_comments = {} |
|
732 | 820 | c.outdated_cnt = 0 |
|
733 | if ChangesetCommentsModel.use_outdated_comments(c.pull_request): | |
|
821 | ||
|
822 | if ChangesetCommentsModel.use_outdated_comments(pull_request): | |
|
734 | 823 | c.outdated_comments = cc_model.get_outdated_comments( |
|
735 | 824 | c.rhodecode_db_repo.repo_id, |
|
736 |
pull_request= |
|
|
825 | pull_request=pull_request) | |
|
826 | ||
|
737 | 827 | # Count outdated comments and check for deleted files |
|
738 | 828 | for file_name, lines in c.outdated_comments.iteritems(): |
|
739 | 829 | for comments in lines.values(): |
|
830 | comments = [comm for comm in comments | |
|
831 | if comm.outdated_at_version(at_version)] | |
|
740 | 832 | c.outdated_cnt += len(comments) |
|
741 | 833 | if file_name not in c.included_files: |
|
742 | 834 | c.deleted_files.append(file_name) |
|
743 | 835 | |
|
744 | ||
|
745 | 836 | # this is a hack to properly display links, when creating PR, the |
|
746 | 837 | # compare view and others uses different notation, and |
|
747 | 838 | # compare_commits.html renders links based on the target_repo. |
@@ -760,6 +851,9 b' class PullrequestsController(BaseRepoCon' | |||
|
760 | 851 | c.commit_statuses = statuses |
|
761 | 852 | |
|
762 | 853 | c.ancestor = None # TODO: add ancestor here |
|
854 | c.pull_request = pull_request_ver | |
|
855 | c.pull_request_latest = pull_request_latest | |
|
856 | c.at_version = at_version | |
|
763 | 857 | |
|
764 | 858 | return render('/pullrequests/pullrequest_show.html') |
|
765 | 859 | |
@@ -813,8 +907,6 b' class PullrequestsController(BaseRepoCon' | |||
|
813 | 907 | closing_pr=close_pr |
|
814 | 908 | ) |
|
815 | 909 | |
|
816 | ||
|
817 | ||
|
818 | 910 | if allowed_to_change_status: |
|
819 | 911 | old_calculated_status = pull_request.calculated_review_status() |
|
820 | 912 | # get status if set ! |
@@ -656,6 +656,16 b' def extract_mentioned_users(s):' | |||
|
656 | 656 | return sorted(list(usrs), key=lambda k: k.lower()) |
|
657 | 657 | |
|
658 | 658 | |
|
659 | class UnsafeAttributeDict(dict): | |
|
660 | def __getattr__(self, attr): | |
|
661 | try: | |
|
662 | return self[attr] | |
|
663 | except KeyError: | |
|
664 | raise AttributeError('%s object has no attribute %s' % (self, attr)) | |
|
665 | __setattr__ = dict.__setitem__ | |
|
666 | __delattr__ = dict.__delitem__ | |
|
667 | ||
|
668 | ||
|
659 | 669 | class AttributeDict(dict): |
|
660 | 670 | def __getattr__(self, attr): |
|
661 | 671 | return self.get(attr, None) |
@@ -90,7 +90,7 b' class BaseModel(object):' | |||
|
90 | 90 | """ |
|
91 | 91 | Gets instance of given cls using some simple lookup mechanism. |
|
92 | 92 | |
|
93 | :param cls: class to fetch | |
|
93 | :param cls: classes to fetch | |
|
94 | 94 | :param instance: int or Instance |
|
95 | 95 | :param callback: callback to call if all lookups failed |
|
96 | 96 | """ |
@@ -98,6 +98,9 b' class BaseModel(object):' | |||
|
98 | 98 | if isinstance(instance, cls): |
|
99 | 99 | return instance |
|
100 | 100 | elif isinstance(instance, (int, long)): |
|
101 | if isinstance(cls, tuple): | |
|
102 | # if we pass multi instances we pick first to .get() | |
|
103 | cls = cls[0] | |
|
101 | 104 | return cls.get(instance) |
|
102 | 105 | else: |
|
103 | 106 | if instance: |
@@ -2933,6 +2933,12 b' class ChangesetComment(Base, BaseModel):' | |||
|
2933 | 2933 | def outdated(self): |
|
2934 | 2934 | return self.display_state == self.COMMENT_OUTDATED |
|
2935 | 2935 | |
|
2936 | def outdated_at_version(self, version): | |
|
2937 | """ | |
|
2938 | Checks if comment is outdated for given pull request version | |
|
2939 | """ | |
|
2940 | return self.outdated and self.pull_request_version_id != version | |
|
2941 | ||
|
2936 | 2942 | def render(self, mentions=False): |
|
2937 | 2943 | from rhodecode.lib import helpers as h |
|
2938 | 2944 | return h.render(self.text, renderer=self.renderer, mentions=mentions) |
@@ -3117,34 +3123,6 b' class _PullRequestBase(BaseModel):' | |||
|
3117 | 3123 | else: |
|
3118 | 3124 | return None |
|
3119 | 3125 | |
|
3120 | ||
|
3121 | class PullRequest(Base, _PullRequestBase): | |
|
3122 | __tablename__ = 'pull_requests' | |
|
3123 | __table_args__ = ( | |
|
3124 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
|
3125 | 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
|
3126 | ) | |
|
3127 | ||
|
3128 | pull_request_id = Column( | |
|
3129 | 'pull_request_id', Integer(), nullable=False, primary_key=True) | |
|
3130 | ||
|
3131 | def __repr__(self): | |
|
3132 | if self.pull_request_id: | |
|
3133 | return '<DB:PullRequest #%s>' % self.pull_request_id | |
|
3134 | else: | |
|
3135 | return '<DB:PullRequest at %#x>' % id(self) | |
|
3136 | ||
|
3137 | reviewers = relationship('PullRequestReviewers', | |
|
3138 | cascade="all, delete, delete-orphan") | |
|
3139 | statuses = relationship('ChangesetStatus') | |
|
3140 | comments = relationship('ChangesetComment', | |
|
3141 | cascade="all, delete, delete-orphan") | |
|
3142 | versions = relationship('PullRequestVersion', | |
|
3143 | cascade="all, delete, delete-orphan") | |
|
3144 | ||
|
3145 | def is_closed(self): | |
|
3146 | return self.status == self.STATUS_CLOSED | |
|
3147 | ||
|
3148 | 3126 | def get_api_data(self): |
|
3149 | 3127 | from rhodecode.model.pull_request import PullRequestModel |
|
3150 | 3128 | pull_request = self |
@@ -3209,6 +3187,35 b' class PullRequest(Base, _PullRequestBase' | |||
|
3209 | 3187 | |
|
3210 | 3188 | return data |
|
3211 | 3189 | |
|
3190 | ||
|
3191 | class PullRequest(Base, _PullRequestBase): | |
|
3192 | __tablename__ = 'pull_requests' | |
|
3193 | __table_args__ = ( | |
|
3194 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
|
3195 | 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
|
3196 | ) | |
|
3197 | ||
|
3198 | pull_request_id = Column( | |
|
3199 | 'pull_request_id', Integer(), nullable=False, primary_key=True) | |
|
3200 | ||
|
3201 | def __repr__(self): | |
|
3202 | if self.pull_request_id: | |
|
3203 | return '<DB:PullRequest #%s>' % self.pull_request_id | |
|
3204 | else: | |
|
3205 | return '<DB:PullRequest at %#x>' % id(self) | |
|
3206 | ||
|
3207 | reviewers = relationship('PullRequestReviewers', | |
|
3208 | cascade="all, delete, delete-orphan") | |
|
3209 | statuses = relationship('ChangesetStatus') | |
|
3210 | comments = relationship('ChangesetComment', | |
|
3211 | cascade="all, delete, delete-orphan") | |
|
3212 | versions = relationship('PullRequestVersion', | |
|
3213 | cascade="all, delete, delete-orphan", | |
|
3214 | lazy='dynamic') | |
|
3215 | ||
|
3216 | def is_closed(self): | |
|
3217 | return self.status == self.STATUS_CLOSED | |
|
3218 | ||
|
3212 | 3219 | def __json__(self): |
|
3213 | 3220 | return { |
|
3214 | 3221 | 'revisions': self.revisions, |
@@ -3243,6 +3250,24 b' class PullRequestVersion(Base, _PullRequ' | |||
|
3243 | 3250 | else: |
|
3244 | 3251 | return '<DB:PullRequestVersion at %#x>' % id(self) |
|
3245 | 3252 | |
|
3253 | @property | |
|
3254 | def reviewers(self): | |
|
3255 | return self.pull_request.reviewers | |
|
3256 | ||
|
3257 | @property | |
|
3258 | def versions(self): | |
|
3259 | return self.pull_request.versions | |
|
3260 | ||
|
3261 | def is_closed(self): | |
|
3262 | # calculate from original | |
|
3263 | return self.pull_request.status == self.STATUS_CLOSED | |
|
3264 | ||
|
3265 | def calculated_review_status(self): | |
|
3266 | return self.pull_request.calculated_review_status() | |
|
3267 | ||
|
3268 | def reviewers_statuses(self): | |
|
3269 | return self.pull_request.reviewers_statuses() | |
|
3270 | ||
|
3246 | 3271 | |
|
3247 | 3272 | class PullRequestReviewers(Base, BaseModel): |
|
3248 | 3273 | __tablename__ = 'pull_request_reviewers' |
@@ -208,8 +208,14 b' input[type="button"] {' | |||
|
208 | 208 | color: @rcdarkblue; |
|
209 | 209 | } |
|
210 | 210 | |
|
211 | //disabled buttons | |
|
212 | //last; overrides any other styles | |
|
211 | 213 | &:disabled { |
|
214 | opacity: .7; | |
|
215 | cursor: auto; | |
|
216 | background-color: white; | |
|
212 | 217 | color: @grey4; |
|
218 | text-shadow: none; | |
|
213 | 219 | } |
|
214 | 220 | |
|
215 | 221 | // TODO: johbo: Check if we can avoid this, indicates that the structure |
@@ -313,7 +313,7 b' table.code-difftable {' | |||
|
313 | 313 | // Comments |
|
314 | 314 | |
|
315 | 315 | div.comment:target { |
|
316 | border-left: 6px solid @comment-highlight-color; | |
|
316 | border-left: 6px solid @comment-highlight-color !important; | |
|
317 | 317 | padding-left: 3px; |
|
318 | 318 | margin-left: -9px; |
|
319 | 319 | } |
@@ -737,6 +737,15 b' input.filediff-collapse-state {' | |||
|
737 | 737 | } |
|
738 | 738 | } |
|
739 | 739 | |
|
740 | /* Main comments*/ | |
|
741 | #comments { | |
|
742 | .comment-selected { | |
|
743 | border-left: 6px solid @comment-highlight-color; | |
|
744 | padding-left: 3px; | |
|
745 | margin-left: -9px; | |
|
746 | } | |
|
747 | } | |
|
748 | ||
|
740 | 749 | .filediff { |
|
741 | 750 | border: 1px solid @grey5; |
|
742 | 751 | |
@@ -894,6 +903,7 b' input.filediff-collapse-state {' | |||
|
894 | 903 | display: none; |
|
895 | 904 | } |
|
896 | 905 | } |
|
906 | ||
|
897 | 907 | .inline-comments { |
|
898 | 908 | border-radius: @border-radius; |
|
899 | 909 | background: @grey6; |
@@ -904,6 +914,7 b' input.filediff-collapse-state {' | |||
|
904 | 914 | .comment-outdated { |
|
905 | 915 | opacity: 0.5; |
|
906 | 916 | } |
|
917 | ||
|
907 | 918 | .comment-inline { |
|
908 | 919 | background: white; |
|
909 | 920 | padding: (@comment-padding + 3px) @comment-padding; |
@@ -170,22 +170,6 b' tr.inline-comments div {' | |||
|
170 | 170 | color: @rcblue; |
|
171 | 171 | } |
|
172 | 172 | |
|
173 | .outdated { | |
|
174 | display: none; | |
|
175 | opacity: 0.6; | |
|
176 | ||
|
177 | .comment { | |
|
178 | margin: 0 0 @padding; | |
|
179 | ||
|
180 | .date:after { | |
|
181 | content: none; | |
|
182 | } | |
|
183 | } | |
|
184 | .outdated_comment_block { | |
|
185 | padding: 0 0 @space 0; | |
|
186 | } | |
|
187 | } | |
|
188 | ||
|
189 | 173 | // Comment Form |
|
190 | 174 | div.comment-form { |
|
191 | 175 | margin-top: 20px; |
@@ -1395,9 +1395,7 b' table.integrations {' | |||
|
1395 | 1395 | width: 92%; |
|
1396 | 1396 | margin-bottom: 1em; |
|
1397 | 1397 | } |
|
1398 | #update_commits { | |
|
1399 | float: right; | |
|
1400 | } | |
|
1398 | ||
|
1401 | 1399 | .compare_view_commits tr{ |
|
1402 | 1400 | height: 20px; |
|
1403 | 1401 | } |
@@ -48,58 +48,6 b' var tableTr = function(cls, body){' | |||
|
48 | 48 | return _el.children[0].children[0].children[0]; |
|
49 | 49 | }; |
|
50 | 50 | |
|
51 | var removeInlineForm = function(form) { | |
|
52 | form.parentNode.removeChild(form); | |
|
53 | }; | |
|
54 | ||
|
55 | var createInlineForm = function(parent_tr, f_path, line) { | |
|
56 | var tmpl = $('#comment-inline-form-template').html(); | |
|
57 | tmpl = tmpl.format(f_path, line); | |
|
58 | var form = tableTr('comment-form-inline', tmpl); | |
|
59 | var form_hide_button = $(form).find('.hide-inline-form'); | |
|
60 | ||
|
61 | $(form_hide_button).click(function(e) { | |
|
62 | $('.inline-comments').removeClass('hide-comment-button'); | |
|
63 | var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode; | |
|
64 | if ($(newtr.nextElementSibling).hasClass('inline-comments-button')) { | |
|
65 | $(newtr.nextElementSibling).show(); | |
|
66 | } | |
|
67 | $(newtr).parents('.comment-form-inline').remove(); | |
|
68 | $(parent_tr).removeClass('form-open'); | |
|
69 | $(parent_tr).removeClass('hl-comment'); | |
|
70 | }); | |
|
71 | ||
|
72 | return form; | |
|
73 | }; | |
|
74 | ||
|
75 | var getLineNo = function(tr) { | |
|
76 | var line; | |
|
77 | // Try to get the id and return "" (empty string) if it doesn't exist | |
|
78 | var o = ($(tr).find('.lineno.old').attr('id')||"").split('_'); | |
|
79 | var n = ($(tr).find('.lineno.new').attr('id')||"").split('_'); | |
|
80 | if (n.length >= 2) { | |
|
81 | line = n[n.length-1]; | |
|
82 | } else if (o.length >= 2) { | |
|
83 | line = o[o.length-1]; | |
|
84 | } | |
|
85 | return line; | |
|
86 | }; | |
|
87 | ||
|
88 | /** | |
|
89 | * make a single inline comment and place it inside | |
|
90 | */ | |
|
91 | var renderInlineComment = function(json_data, show_add_button) { | |
|
92 | show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true; | |
|
93 | try { | |
|
94 | var html = json_data.rendered_text; | |
|
95 | var lineno = json_data.line_no; | |
|
96 | var target_id = json_data.target_id; | |
|
97 | placeInline(target_id, lineno, html, show_add_button); | |
|
98 | } catch (e) { | |
|
99 | console.error(e); | |
|
100 | } | |
|
101 | }; | |
|
102 | ||
|
103 | 51 | function bindDeleteCommentButtons() { |
|
104 | 52 | $('.delete-comment').one('click', function() { |
|
105 | 53 | var comment_id = $(this).data("comment-id"); |
@@ -110,115 +58,6 b' function bindDeleteCommentButtons() {' | |||
|
110 | 58 | }); |
|
111 | 59 | } |
|
112 | 60 | |
|
113 | /** | |
|
114 | * Inject inline comment for on given TR this tr should be always an .line | |
|
115 | * tr containing the line. Code will detect comment, and always put the comment | |
|
116 | * block at the very bottom | |
|
117 | */ | |
|
118 | var injectInlineForm = function(tr){ | |
|
119 | if (!$(tr).hasClass('line')) { | |
|
120 | return; | |
|
121 | } | |
|
122 | ||
|
123 | var _td = $(tr).find('.code').get(0); | |
|
124 | if ($(tr).hasClass('form-open') || | |
|
125 | $(tr).hasClass('context') || | |
|
126 | $(_td).hasClass('no-comment')) { | |
|
127 | return; | |
|
128 | } | |
|
129 | $(tr).addClass('form-open'); | |
|
130 | $(tr).addClass('hl-comment'); | |
|
131 | var node = $(tr.parentNode.parentNode.parentNode).find('.full_f_path').get(0); | |
|
132 | var f_path = $(node).attr('path'); | |
|
133 | var lineno = getLineNo(tr); | |
|
134 | var form = createInlineForm(tr, f_path, lineno); | |
|
135 | ||
|
136 | var parent = tr; | |
|
137 | while (1) { | |
|
138 | var n = parent.nextElementSibling; | |
|
139 | // next element are comments ! | |
|
140 | if ($(n).hasClass('inline-comments')) { | |
|
141 | parent = n; | |
|
142 | } | |
|
143 | else { | |
|
144 | break; | |
|
145 | } | |
|
146 | } | |
|
147 | var _parent = $(parent).get(0); | |
|
148 | $(_parent).after(form); | |
|
149 | $('.comment-form-inline').prev('.inline-comments').addClass('hide-comment-button'); | |
|
150 | var f = $(form).get(0); | |
|
151 | ||
|
152 | var _form = $(f).find('.inline-form').get(0); | |
|
153 | ||
|
154 | var pullRequestId = templateContext.pull_request_data.pull_request_id; | |
|
155 | var commitId = templateContext.commit_data.commit_id; | |
|
156 | ||
|
157 | var commentForm = new CommentForm(_form, commitId, pullRequestId, lineno, false); | |
|
158 | var cm = commentForm.getCmInstance(); | |
|
159 | ||
|
160 | // set a CUSTOM submit handler for inline comments. | |
|
161 | commentForm.setHandleFormSubmit(function(o) { | |
|
162 | var text = commentForm.cm.getValue(); | |
|
163 | ||
|
164 | if (text === "") { | |
|
165 | return; | |
|
166 | } | |
|
167 | ||
|
168 | if (lineno === undefined) { | |
|
169 | alert('missing line !'); | |
|
170 | return; | |
|
171 | } | |
|
172 | if (f_path === undefined) { | |
|
173 | alert('missing file path !'); | |
|
174 | return; | |
|
175 | } | |
|
176 | ||
|
177 | var excludeCancelBtn = false; | |
|
178 | var submitEvent = true; | |
|
179 | commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent); | |
|
180 | commentForm.cm.setOption("readOnly", true); | |
|
181 | var postData = { | |
|
182 | 'text': text, | |
|
183 | 'f_path': f_path, | |
|
184 | 'line': lineno, | |
|
185 | 'csrf_token': CSRF_TOKEN | |
|
186 | }; | |
|
187 | var submitSuccessCallback = function(o) { | |
|
188 | $(tr).removeClass('form-open'); | |
|
189 | removeInlineForm(f); | |
|
190 | renderInlineComment(o); | |
|
191 | $('.inline-comments').removeClass('hide-comment-button'); | |
|
192 | ||
|
193 | // re trigger the linkification of next/prev navigation | |
|
194 | linkifyComments($('.inline-comment-injected')); | |
|
195 | timeagoActivate(); | |
|
196 | bindDeleteCommentButtons(); | |
|
197 | commentForm.setActionButtonsDisabled(false); | |
|
198 | ||
|
199 | }; | |
|
200 | var submitFailCallback = function(){ | |
|
201 | commentForm.resetCommentFormState(text) | |
|
202 | }; | |
|
203 | commentForm.submitAjaxPOST( | |
|
204 | commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback); | |
|
205 | }); | |
|
206 | ||
|
207 | setTimeout(function() { | |
|
208 | // callbacks | |
|
209 | if (cm !== undefined) { | |
|
210 | cm.focus(); | |
|
211 | } | |
|
212 | }, 10); | |
|
213 | ||
|
214 | $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({ | |
|
215 | form:_form, | |
|
216 | parent:_parent, | |
|
217 | lineno: lineno, | |
|
218 | f_path: f_path} | |
|
219 | ); | |
|
220 | }; | |
|
221 | ||
|
222 | 61 | var deleteComment = function(comment_id) { |
|
223 | 62 | var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id); |
|
224 | 63 | var postData = { |
@@ -232,89 +71,6 b' var deleteComment = function(comment_id)' | |||
|
232 | 71 | ajaxPOST(url, postData, success); |
|
233 | 72 | }; |
|
234 | 73 | |
|
235 | var createInlineAddButton = function(tr){ | |
|
236 | var label = _gettext('Add another comment'); | |
|
237 | var html_el = document.createElement('div'); | |
|
238 | $(html_el).addClass('add-comment'); | |
|
239 | html_el.innerHTML = '<span class="btn btn-secondary">{0}</span>'.format(label); | |
|
240 | var add = new $(html_el); | |
|
241 | add.on('click', function(e) { | |
|
242 | injectInlineForm(tr); | |
|
243 | }); | |
|
244 | return add; | |
|
245 | }; | |
|
246 | ||
|
247 | var placeAddButton = function(target_tr){ | |
|
248 | if(!target_tr){ | |
|
249 | return; | |
|
250 | } | |
|
251 | var last_node = target_tr; | |
|
252 | // scan | |
|
253 | while (1){ | |
|
254 | var n = last_node.nextElementSibling; | |
|
255 | // next element are comments ! | |
|
256 | if($(n).hasClass('inline-comments')){ | |
|
257 | last_node = n; | |
|
258 | // also remove the comment button from previous | |
|
259 | var comment_add_buttons = $(last_node).find('.add-comment'); | |
|
260 | for(var i=0; i<comment_add_buttons.length; i++){ | |
|
261 | var b = comment_add_buttons[i]; | |
|
262 | b.parentNode.removeChild(b); | |
|
263 | } | |
|
264 | } | |
|
265 | else{ | |
|
266 | break; | |
|
267 | } | |
|
268 | } | |
|
269 | var add = createInlineAddButton(target_tr); | |
|
270 | // get the comment div | |
|
271 | var comment_block = $(last_node).find('.comment')[0]; | |
|
272 | // attach add button | |
|
273 | $(add).insertAfter(comment_block); | |
|
274 | }; | |
|
275 | ||
|
276 | /** | |
|
277 | * Places the inline comment into the changeset block in proper line position | |
|
278 | */ | |
|
279 | var placeInline = function(target_container, lineno, html, show_add_button) { | |
|
280 | show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true; | |
|
281 | ||
|
282 | var lineid = "{0}_{1}".format(target_container, lineno); | |
|
283 | var target_line = $('#' + lineid).get(0); | |
|
284 | var comment = new $(tableTr('inline-comments', html)); | |
|
285 | // check if there are comments already ! | |
|
286 | if (target_line) { | |
|
287 | var parent_node = target_line.parentNode; | |
|
288 | var root_parent = parent_node; | |
|
289 | ||
|
290 | while (1) { | |
|
291 | var n = parent_node.nextElementSibling; | |
|
292 | // next element are comments ! | |
|
293 | if ($(n).hasClass('inline-comments')) { | |
|
294 | parent_node = n; | |
|
295 | } | |
|
296 | else { | |
|
297 | break; | |
|
298 | } | |
|
299 | } | |
|
300 | // put in the comment at the bottom | |
|
301 | $(comment).insertAfter(parent_node); | |
|
302 | $(comment).find('.comment-inline').addClass('inline-comment-injected'); | |
|
303 | // scan nodes, and attach add button to last one | |
|
304 | if (show_add_button) { | |
|
305 | placeAddButton(root_parent); | |
|
306 | } | |
|
307 | addCommentToggle(target_line); | |
|
308 | } | |
|
309 | ||
|
310 | return target_line; | |
|
311 | }; | |
|
312 | ||
|
313 | var addCommentToggle = function(target_line) { | |
|
314 | // exposes comment toggle button | |
|
315 | $(target_line).siblings('.comment-toggle').addClass('active'); | |
|
316 | return; | |
|
317 | }; | |
|
318 | 74 | |
|
319 | 75 | var bindToggleButtons = function() { |
|
320 | 76 | $('.comment-toggle').on('click', function() { |
@@ -348,37 +104,6 b' var linkifyComments = function(comments)' | |||
|
348 | 104 | |
|
349 | 105 | }; |
|
350 | 106 | |
|
351 | /** | |
|
352 | * Iterates over all the inlines, and places them inside proper blocks of data | |
|
353 | */ | |
|
354 | var renderInlineComments = function(file_comments, show_add_button) { | |
|
355 | show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true; | |
|
356 | ||
|
357 | for (var i = 0; i < file_comments.length; i++) { | |
|
358 | var box = file_comments[i]; | |
|
359 | ||
|
360 | var target_id = $(box).attr('target_id'); | |
|
361 | ||
|
362 | // actually comments with line numbers | |
|
363 | var comments = box.children; | |
|
364 | ||
|
365 | for (var j = 0; j < comments.length; j++) { | |
|
366 | var data = { | |
|
367 | 'rendered_text': comments[j].outerHTML, | |
|
368 | 'line_no': $(comments[j]).attr('line'), | |
|
369 | 'target_id': target_id | |
|
370 | }; | |
|
371 | renderInlineComment(data, show_add_button); | |
|
372 | } | |
|
373 | } | |
|
374 | ||
|
375 | // since order of injection is random, we're now re-iterating | |
|
376 | // from correct order and filling in links | |
|
377 | linkifyComments($('.inline-comment-injected')); | |
|
378 | bindDeleteCommentButtons(); | |
|
379 | firefoxAnchorFix(); | |
|
380 | }; | |
|
381 | ||
|
382 | 107 | |
|
383 | 108 | /* Comment form for main and inline comments */ |
|
384 | 109 | var CommentForm = (function() { |
@@ -679,11 +404,13 b' var CommentsController = function() { /*' | |||
|
679 | 404 | var $td = $node.closest('td'); |
|
680 | 405 | $node.closest('.comment-inline-form').removeClass('comment-inline-form-open'); |
|
681 | 406 | return false; |
|
682 | } | |
|
407 | }; | |
|
408 | ||
|
683 | 409 | this.getLineNumber = function(node) { |
|
684 | 410 | var $node = $(node); |
|
685 | 411 | return $node.closest('td').attr('data-line-number'); |
|
686 | } | |
|
412 | }; | |
|
413 | ||
|
687 | 414 | this.scrollToComment = function(node, offset) { |
|
688 | 415 | if (!node) { |
|
689 | 416 | node = $('.comment-selected'); |
@@ -702,20 +429,23 b' var CommentsController = function() { /*' | |||
|
702 | 429 | } |
|
703 | 430 | var $next = $('.comment-current').eq(nextIdx); |
|
704 | 431 | var $cb = $next.closest('.cb'); |
|
705 | $cb.removeClass('cb-collapsed') | |
|
432 | $cb.removeClass('cb-collapsed'); | |
|
706 | 433 | |
|
707 | 434 | var $filediffCollapseState = $cb.closest('.filediff').prev(); |
|
708 | 435 | $filediffCollapseState.prop('checked', false); |
|
709 | 436 | $next.addClass('comment-selected'); |
|
710 | 437 | scrollToElement($next); |
|
711 | 438 | return false; |
|
712 | } | |
|
439 | }; | |
|
440 | ||
|
713 | 441 | this.nextComment = function(node) { |
|
714 | 442 | return self.scrollToComment(node, 1); |
|
715 | } | |
|
443 | }; | |
|
444 | ||
|
716 | 445 | this.prevComment = function(node) { |
|
717 | 446 | return self.scrollToComment(node, -1); |
|
718 | } | |
|
447 | }; | |
|
448 | ||
|
719 | 449 | this.deleteComment = function(node) { |
|
720 | 450 | if (!confirm(_gettext('Delete this comment?'))) { |
|
721 | 451 | return false; |
@@ -744,7 +474,8 b' var CommentsController = function() { /*' | |||
|
744 | 474 | return false; |
|
745 | 475 | }; |
|
746 | 476 | ajaxPOST(url, postData, success, failure); |
|
747 | } | |
|
477 | }; | |
|
478 | ||
|
748 | 479 | this.toggleComments = function(node, show) { |
|
749 | 480 | var $filediff = $(node).closest('.filediff'); |
|
750 | 481 | if (show === true) { |
@@ -757,12 +488,14 b' var CommentsController = function() { /*' | |||
|
757 | 488 | $filediff.toggleClass('hide-comments'); |
|
758 | 489 | } |
|
759 | 490 | return false; |
|
760 | } | |
|
491 | }; | |
|
492 | ||
|
761 | 493 | this.toggleLineComments = function(node) { |
|
762 | 494 | self.toggleComments(node, true); |
|
763 | 495 | var $node = $(node); |
|
764 | 496 | $node.closest('tr').toggleClass('hide-line-comments'); |
|
765 | } | |
|
497 | }; | |
|
498 | ||
|
766 | 499 | this.createComment = function(node) { |
|
767 | 500 | var $node = $(node); |
|
768 | 501 | var $td = $node.closest('td'); |
@@ -832,7 +565,6 b' var CommentsController = function() { /*' | |||
|
832 | 565 | console.error(e); |
|
833 | 566 | } |
|
834 | 567 | |
|
835 | ||
|
836 | 568 | // re trigger the linkification of next/prev navigation |
|
837 | 569 | linkifyComments($('.inline-comment-injected')); |
|
838 | 570 | timeagoActivate(); |
@@ -863,7 +595,7 b' var CommentsController = function() { /*' | |||
|
863 | 595 | } |
|
864 | 596 | |
|
865 | 597 | $form.addClass('comment-inline-form-open'); |
|
866 | } | |
|
598 | }; | |
|
867 | 599 | |
|
868 | 600 | this.renderInlineComments = function(file_comments) { |
|
869 | 601 | show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true; |
@@ -892,4 +624,4 b' var CommentsController = function() { /*' | |||
|
892 | 624 | firefoxAnchorFix(); |
|
893 | 625 | }; |
|
894 | 626 | |
|
895 | } No newline at end of file | |
|
627 | }; No newline at end of file |
@@ -178,9 +178,8 b'' | |||
|
178 | 178 | |
|
179 | 179 | ## template for inline comment form |
|
180 | 180 | <%namespace name="comment" file="/changeset/changeset_file_comment.html"/> |
|
181 | ${comment.comment_inline_form()} | |
|
182 | 181 | |
|
183 |
## |
|
|
182 | ## render comments | |
|
184 | 183 | ${comment.generate_comments()} |
|
185 | 184 | |
|
186 | 185 | ## main comment form and it status |
@@ -210,12 +209,12 b'' | |||
|
210 | 209 | if(button.hasClass("comments-visible")) { |
|
211 | 210 | $('#{0} .inline-comments'.format(boxid)).each(function(index){ |
|
212 | 211 | $(this).hide(); |
|
213 | }) | |
|
212 | }); | |
|
214 | 213 | button.removeClass("comments-visible"); |
|
215 | 214 | } else { |
|
216 | 215 | $('#{0} .inline-comments'.format(boxid)).each(function(index){ |
|
217 | 216 | $(this).show(); |
|
218 | }) | |
|
217 | }); | |
|
219 | 218 | button.addClass("comments-visible"); |
|
220 | 219 | } |
|
221 | 220 | }); |
@@ -230,7 +229,7 b'' | |||
|
230 | 229 | url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}', |
|
231 | 230 | success: function(data) { |
|
232 | 231 | if(data.results.length === 0){ |
|
233 |
$('#child_link').html( |
|
|
232 | $('#child_link').html("${_('No Child Commits')}").addClass('disabled'); | |
|
234 | 233 | } |
|
235 | 234 | if(data.results.length === 1){ |
|
236 | 235 | var commit = data.results[0]; |
@@ -263,7 +262,7 b'' | |||
|
263 | 262 | // >1 links show them to user to choose |
|
264 | 263 | if(!$('#parent_link').hasClass('disabled')){ |
|
265 | 264 | $.ajax({ |
|
266 |
url: '${h.url( |
|
|
265 | url: '${h.url("changeset_parents",repo_name=c.repo_name, revision=c.commit.raw_id)}', | |
|
267 | 266 | success: function(data) { |
|
268 | 267 | if(data.results.length === 0){ |
|
269 | 268 | $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled'); |
@@ -6,14 +6,14 b'' | |||
|
6 | 6 | <%namespace name="base" file="/base/base.html"/> |
|
7 | 7 | |
|
8 | 8 | <%def name="comment_block(comment, inline=False)"> |
|
9 | <div | |
|
10 | class="comment | |
|
11 | ${'comment-inline' if inline else ''} | |
|
12 | ${'comment-outdated' if comment.outdated else 'comment-current'}" | |
|
13 | " | |
|
14 |
id=" |
|
|
15 | line="${comment.line_no}" | |
|
16 | data-comment-id="${comment.comment_id}"> | |
|
9 | <div class="comment | |
|
10 | ${'comment-inline' if inline else ''} | |
|
11 | ${'comment-outdated' if comment.outdated_at_version(getattr(c, 'at_version', None)) else 'comment-current'}" | |
|
12 | id="comment-${comment.comment_id}" | |
|
13 | line="${comment.line_no}" | |
|
14 | data-comment-id="${comment.comment_id}" | |
|
15 | style="${'display: none;' if comment.outdated_at_version(getattr(c, 'at_version', None)) else ''}"> | |
|
16 | ||
|
17 | 17 | <div class="meta"> |
|
18 | 18 | <div class="author"> |
|
19 | 19 | ${base.gravatar_with_user(comment.author.email, 16)} |
@@ -22,21 +22,27 b'' | |||
|
22 | 22 | ${h.age_component(comment.modified_at, time_is_local=True)} |
|
23 | 23 | </div> |
|
24 | 24 | <div class="status-change"> |
|
25 | %if comment.pull_request: | |
|
26 | <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}"> | |
|
27 | %if comment.status_change: | |
|
28 |
|
|
|
29 |
|
|
|
30 | ${_('Comment on pull request #%s') % comment.pull_request.pull_request_id} | |
|
31 | %endif | |
|
32 | </a> | |
|
33 | %else: | |
|
34 |
|
|
|
25 | % if comment.pull_request: | |
|
26 | % if comment.outdated: | |
|
27 | <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"> | |
|
28 | ${_('Outdated comment from pull request version {}').format(comment.pull_request_version_id)} | |
|
29 | </a> | |
|
30 | % else: | |
|
31 | <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}"> | |
|
32 | %if comment.status_change: | |
|
33 | ${_('Vote on pull request #%s') % comment.pull_request.pull_request_id}: | |
|
34 | %else: | |
|
35 | ${_('Comment on pull request #%s') % comment.pull_request.pull_request_id} | |
|
36 | %endif | |
|
37 | </a> | |
|
38 | % endif | |
|
39 | % else: | |
|
40 | % if comment.status_change: | |
|
35 | 41 | ${_('Status change on commit')}: |
|
36 | %else: | |
|
42 | % else: | |
|
37 | 43 | ${_('Comment on commit')} |
|
38 | %endif | |
|
39 | %endif | |
|
44 | % endif | |
|
45 | % endif | |
|
40 | 46 | </div> |
|
41 | 47 | %if comment.status_change: |
|
42 | 48 | <div class="${'flag_status %s' % comment.status_change[0].status}"></div> |
@@ -52,14 +58,19 b'' | |||
|
52 | 58 | ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed |
|
53 | 59 | ## only super-admin, repo admin OR comment owner can delete |
|
54 | 60 | %if not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed()): |
|
61 | ## permissions to delete | |
|
55 | 62 | %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id: |
|
56 | 63 | ## TODO: dan: add edit comment here |
|
57 |
<a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a> |
|
|
58 | %if not comment.outdated: | |
|
59 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a> | | |
|
60 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a> | |
|
61 | %endif | |
|
64 | <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a> | |
|
65 | %else: | |
|
66 | <button class="btn-link" disabled="disabled"> ${_('Delete')}</button> | |
|
62 | 67 | %endif |
|
68 | %else: | |
|
69 | <button class="btn-link" disabled="disabled"> ${_('Delete')}</button> | |
|
70 | %endif | |
|
71 | %if not comment.outdated_at_version(getattr(c, 'at_version', None)): | |
|
72 | | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a> | |
|
73 | | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a> | |
|
63 | 74 | %endif |
|
64 | 75 | |
|
65 | 76 | </div> |
@@ -67,102 +78,9 b'' | |||
|
67 | 78 | <div class="text"> |
|
68 | 79 | ${comment.render(mentions=True)|n} |
|
69 | 80 | </div> |
|
70 | </div> | |
|
71 | </%def> | |
|
72 | 81 | |
|
73 | <%def name="comment_block_outdated(comment)"> | |
|
74 | <div class="comments" id="comment-${comment.comment_id}"> | |
|
75 | <div class="comment comment-wrapp"> | |
|
76 | <div class="meta"> | |
|
77 | <div class="author"> | |
|
78 | ${base.gravatar_with_user(comment.author.email, 16)} | |
|
79 | </div> | |
|
80 | <div class="date"> | |
|
81 | ${h.age_component(comment.modified_at, time_is_local=True)} | |
|
82 | </div> | |
|
83 | %if comment.status_change: | |
|
84 | <span class="changeset-status-container"> | |
|
85 | <span class="changeset-status-ico"> | |
|
86 | <div class="${'flag_status %s' % comment.status_change[0].status}"></div> | |
|
87 | </span> | |
|
88 | <span title="${_('Commit status')}" class="changeset-status-lbl"> ${comment.status_change[0].status_lbl}</span> | |
|
89 | </span> | |
|
90 | %endif | |
|
91 | <a class="permalink" href="#comment-${comment.comment_id}">¶</a> | |
|
92 | ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed | |
|
93 | ## only super-admin, repo admin OR comment owner can delete | |
|
94 | %if not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed()): | |
|
95 | <div class="comment-links-block"> | |
|
96 | %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id: | |
|
97 | <div data-comment-id=${comment.comment_id} class="delete-comment">${_('Delete')}</div> | |
|
98 | %endif | |
|
99 | </div> | |
|
100 | %endif | |
|
101 | </div> | |
|
102 | <div class="text"> | |
|
103 | ${comment.render(mentions=True)|n} | |
|
104 | </div> | |
|
105 | </div> | |
|
106 | 82 | </div> |
|
107 | 83 | </%def> |
|
108 | ||
|
109 | <%def name="comment_inline_form()"> | |
|
110 | <div id="comment-inline-form-template" style="display: none;"> | |
|
111 | <div class="comment-inline-form ac"> | |
|
112 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
|
113 | ${h.form('#', class_='inline-form', method='get')} | |
|
114 | <div id="edit-container_{1}" class="clearfix"> | |
|
115 | <div class="comment-title pull-left"> | |
|
116 | ${_('Create a comment on line {1}.')} | |
|
117 | </div> | |
|
118 | <div class="comment-help pull-right"> | |
|
119 | ${(_('Comments parsed using %s syntax with %s support.') % ( | |
|
120 | ('<a href="%s">%s</a>' % (h.url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())), | |
|
121 | ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')) | |
|
122 | ) | |
|
123 | )|n | |
|
124 | } | |
|
125 | </div> | |
|
126 | <div style="clear: both"></div> | |
|
127 | <textarea id="text_{1}" name="text" class="comment-block-ta ac-input"></textarea> | |
|
128 | </div> | |
|
129 | <div id="preview-container_{1}" class="clearfix" style="display: none;"> | |
|
130 | <div class="comment-help"> | |
|
131 | ${_('Comment preview')} | |
|
132 | </div> | |
|
133 | <div id="preview-box_{1}" class="preview-box"></div> | |
|
134 | </div> | |
|
135 | <div class="comment-footer"> | |
|
136 | <div class="comment-button hide-inline-form-button cancel-button"> | |
|
137 | ${h.reset('hide-inline-form', _('Cancel'), class_='btn hide-inline-form', id_="cancel-btn_{1}")} | |
|
138 | </div> | |
|
139 | <div class="action-buttons"> | |
|
140 | <input type="hidden" name="f_path" value="{0}"> | |
|
141 | <input type="hidden" name="line" value="{1}"> | |
|
142 | <button id="preview-btn_{1}" class="btn btn-secondary">${_('Preview')}</button> | |
|
143 | <button id="edit-btn_{1}" class="btn btn-secondary" style="display: none;">${_('Edit')}</button> | |
|
144 | ${h.submit('save', _('Comment'), class_='btn btn-success save-inline-form')} | |
|
145 | </div> | |
|
146 | ${h.end_form()} | |
|
147 | </div> | |
|
148 | %else: | |
|
149 | ${h.form('', class_='inline-form comment-form-login', method='get')} | |
|
150 | <div class="pull-left"> | |
|
151 | <div class="comment-help pull-right"> | |
|
152 | ${_('You need to be logged in to comment.')} <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a> | |
|
153 | </div> | |
|
154 | </div> | |
|
155 | <div class="comment-button pull-right"> | |
|
156 | ${h.reset('hide-inline-form', _('Hide'), class_='btn hide-inline-form')} | |
|
157 | </div> | |
|
158 | <div class="clearfix"></div> | |
|
159 | ${h.end_form()} | |
|
160 | %endif | |
|
161 | </div> | |
|
162 | </div> | |
|
163 | </%def> | |
|
164 | ||
|
165 | ||
|
166 | 84 | ## generate main comments |
|
167 | 85 | <%def name="generate_comments(include_pull_request=False, is_pull_request=False)"> |
|
168 | 86 | <div id="comments"> |
@@ -182,6 +100,7 b'' | |||
|
182 | 100 | |
|
183 | 101 | ## MAIN COMMENT FORM |
|
184 | 102 | <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)"> |
|
103 | ||
|
185 | 104 | %if is_compare: |
|
186 | 105 | <% form_id = "comments_form_compare" %> |
|
187 | 106 | %else: |
@@ -394,10 +394,13 b' from rhodecode.lib.diffs import NEW_FILE' | |||
|
394 | 394 | %for comment in comments: |
|
395 | 395 | ${commentblock.comment_block(comment, inline=True)} |
|
396 | 396 | %endfor |
|
397 | ||
|
397 | 398 | <span onclick="return Rhodecode.comments.createComment(this)" |
|
398 |
class="btn btn-secondary cb-comment-add-button" |
|
|
399 | class="btn btn-secondary cb-comment-add-button ${'comment-outdated' if comments and comments[-1].outdated else ''}" | |
|
400 | style="${'display: none;' if comments and comments[-1].outdated else ''}"> | |
|
399 | 401 | ${_('Add another comment')} |
|
400 | 402 | </span> |
|
403 | ||
|
401 | 404 | </div> |
|
402 | 405 | </%def> |
|
403 | 406 |
@@ -918,9 +918,6 b'' | |||
|
918 | 918 | $(btns).each(fn_display); |
|
919 | 919 | }); |
|
920 | 920 | |
|
921 | // inject comments into they proper positions | |
|
922 | var file_comments = $('.inline-comment-placeholder'); | |
|
923 | renderInlineComments(file_comments); | |
|
924 | 921 | var commentTotals = {}; |
|
925 | 922 | $.each(file_comments, function(i, comment) { |
|
926 | 923 | var path = $(comment).attr('path'); |
@@ -165,24 +165,62 b'' | |||
|
165 | 165 | <div> |
|
166 | 166 | <div class="comments-number"> |
|
167 | 167 | %if c.comments: |
|
168 |
<a href="#comments">${ungettext("%d |
|
|
168 | <a href="#comments">${ungettext("%d General Comment", "%d General Comments", len(c.comments)) % len(c.comments)}</a>, | |
|
169 | 169 | %else: |
|
170 |
${ungettext("%d |
|
|
170 | ${ungettext("%d General Comment", "%d General Comments", len(c.comments)) % len(c.comments)} | |
|
171 | 171 | %endif |
|
172 | ||
|
172 | 173 | %if c.inline_cnt: |
|
173 | 174 | <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a> |
|
174 | 175 | %else: |
|
175 | 176 | ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt} |
|
176 | 177 | %endif |
|
177 | 178 | |
|
178 | ||
|
179 | % if c.outdated_cnt: | |
|
180 | ,${ungettext("%d Outdated Comment", "%d Outdated Comments", c.outdated_cnt) % c.outdated_cnt} <span id="show-outdated-comments" class="btn btn-link">${_('(Show)')}</span> | |
|
181 | % endif | |
|
179 | %if c.outdated_cnt: | |
|
180 | , ${ungettext("%d Outdated Comment", "%d Outdated Comments", c.outdated_cnt) % c.outdated_cnt} <span id="show-outdated-comments" class="btn btn-link">${_('(Show)')}</span> | |
|
181 | %endif | |
|
182 | 182 | </div> |
|
183 | 183 | </div> |
|
184 |
|
|
|
184 | </div> | |
|
185 | ||
|
186 | </div> | |
|
187 | ||
|
188 | <div class="field"> | |
|
189 | <div class="label-summary"> | |
|
190 | <label>${_('Versions')}:</label> | |
|
185 | 191 | </div> |
|
192 | <div> | |
|
193 | <table> | |
|
194 | <tr> | |
|
195 | <td> | |
|
196 | % if c.at_version == None: | |
|
197 | <i class="icon-ok link"></i> | |
|
198 | % endif | |
|
199 | </td> | |
|
200 | <td><code><a href="${h.url.current()}">latest</a></code></td> | |
|
201 | <td> | |
|
202 | <code>${c.pull_request_latest.source_ref_parts.commit_id[:6]}</code> | |
|
203 | </td> | |
|
204 | <td>${_('created')} ${h.age_component(c.pull_request.created_on)}</td> | |
|
205 | </tr> | |
|
206 | % for ver in reversed(c.pull_request.versions()): | |
|
207 | <tr> | |
|
208 | <td> | |
|
209 | % if c.at_version == ver.pull_request_version_id: | |
|
210 | <i class="icon-ok link"></i> | |
|
211 | % endif | |
|
212 | </td> | |
|
213 | <td><code><a href="${h.url.current(version=ver.pull_request_version_id)}">version ${ver.pull_request_version_id}</a></code></td> | |
|
214 | <td> | |
|
215 | <code>${ver.source_ref_parts.commit_id[:6]}</code> | |
|
216 | </td> | |
|
217 | <td>${_('created')} ${h.age_component(ver.created_on)}</td> | |
|
218 | </tr> | |
|
219 | % endfor | |
|
220 | </table> | |
|
221 | </div> | |
|
222 | </div> | |
|
223 | ||
|
186 | 224 | <div id="pr-save" class="field" style="display: none;"> |
|
187 | 225 | <div class="label-summary"></div> |
|
188 | 226 | <div class="input"> |
@@ -291,7 +329,9 b'' | |||
|
291 | 329 | % endif |
|
292 | 330 | <div class="compare_view_commits_title"> |
|
293 | 331 | % if c.allowed_to_update and not c.pull_request.is_closed(): |
|
294 |
<button id="update_commits" class="btn |
|
|
332 | <button id="update_commits" class="btn pull-right">${_('Update commits')}</button> | |
|
333 | % else: | |
|
334 | <button class="btn disabled pull-right" disabled="disabled">${_('Update commits')}</button> | |
|
295 | 335 | % endif |
|
296 | 336 | % if len(c.commit_ranges): |
|
297 | 337 | <h2>${ungettext('Compare View: %s commit','Compare View: %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}</h2> |
@@ -305,7 +345,7 b'' | |||
|
305 | 345 | ${cbdiffs.render_diffset( |
|
306 | 346 | c.diffset, use_comments=True, |
|
307 | 347 | collapse_when_files_over=30, |
|
308 |
disable_new_comments= |
|
|
348 | disable_new_comments=not c.allowed_to_comment)} | |
|
309 | 349 | |
|
310 | 350 | </div> |
|
311 | 351 | % endif |
@@ -313,9 +353,8 b'' | |||
|
313 | 353 | |
|
314 | 354 | ## template for inline comment form |
|
315 | 355 | <%namespace name="comment" file="/changeset/changeset_file_comment.html"/> |
|
316 | ${comment.comment_inline_form()} | |
|
317 | 356 | |
|
318 |
## render comments |
|
|
357 | ## render general comments | |
|
319 | 358 | ${comment.generate_comments(include_pull_request=True, is_pull_request=True)} |
|
320 | 359 | |
|
321 | 360 | % if not c.pull_request.is_closed(): |
@@ -394,7 +433,7 b'' | |||
|
394 | 433 | this.closeButton.hide(); |
|
395 | 434 | this.addButton.hide(); |
|
396 | 435 | this.removeButtons.css('visibility', 'hidden'); |
|
397 |
} |
|
|
436 | } | |
|
398 | 437 | }; |
|
399 | 438 | |
|
400 | 439 | PRDetails.init(); |
@@ -402,7 +441,7 b'' | |||
|
402 | 441 | |
|
403 | 442 | $('#show-outdated-comments').on('click', function(e){ |
|
404 | 443 | var button = $(this); |
|
405 | var outdated = $('.outdated'); | |
|
444 | var outdated = $('.comment-outdated'); | |
|
406 | 445 | if (button.html() === "(Show)") { |
|
407 | 446 | button.html("(Hide)"); |
|
408 | 447 | outdated.show(); |
@@ -428,29 +467,6 b'' | |||
|
428 | 467 | $(btns).each(fn_display); |
|
429 | 468 | }); |
|
430 | 469 | |
|
431 | // inject comments into their proper positions | |
|
432 | var file_comments = $('.inline-comment-placeholder'); | |
|
433 | %if c.pull_request.is_closed(): | |
|
434 | renderInlineComments(file_comments, false); | |
|
435 | %else: | |
|
436 | renderInlineComments(file_comments, true); | |
|
437 | %endif | |
|
438 | var commentTotals = {}; | |
|
439 | $.each(file_comments, function(i, comment) { | |
|
440 | var path = $(comment).attr('path'); | |
|
441 | var comms = $(comment).children().length; | |
|
442 | if (path in commentTotals) { | |
|
443 | commentTotals[path] += comms; | |
|
444 | } else { | |
|
445 | commentTotals[path] = comms; | |
|
446 | } | |
|
447 | }); | |
|
448 | $.each(commentTotals, function(path, total) { | |
|
449 | var elem = $('.comment-bubble[data-path="'+ path +'"]'); | |
|
450 | elem.css('visibility', 'visible'); | |
|
451 | elem.html(elem.html() + ' ' + total ); | |
|
452 | }); | |
|
453 | ||
|
454 | 470 | $('#merge_pull_request_form').submit(function() { |
|
455 | 471 | if (!$('#merge_pull_request').attr('disabled')) { |
|
456 | 472 | $('#merge_pull_request').attr('disabled', 'disabled'); |
General Comments 0
You need to be logged in to leave comments.
Login now