##// END OF EJS Templates
pull-requests: added version browsing for pull requests....
marcink -
r1192:5c951e10 default
parent child Browse files
Show More
@@ -45,16 +45,17 b' from rhodecode.lib.auth import ('
45 from rhodecode.lib.channelstream import channelstream_request
45 from rhodecode.lib.channelstream import channelstream_request
46 from rhodecode.lib.compat import OrderedDict
46 from rhodecode.lib.compat import OrderedDict
47 from rhodecode.lib.utils import jsonify
47 from rhodecode.lib.utils import jsonify
48 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool, safe_unicode
48 from rhodecode.lib.utils2 import (
49 safe_int, safe_str, str2bool, safe_unicode, UnsafeAttributeDict)
49 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
50 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
50 from rhodecode.lib.vcs.exceptions import (
51 from rhodecode.lib.vcs.exceptions import (
51 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError,
52 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError,
52 NodeDoesNotExistError)
53 NodeDoesNotExistError)
53 from rhodecode.lib.diffs import LimitedDiffContainer
54
54 from rhodecode.model.changeset_status import ChangesetStatusModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
55 from rhodecode.model.comment import ChangesetCommentsModel
56 from rhodecode.model.comment import ChangesetCommentsModel
56 from rhodecode.model.db import PullRequest, ChangesetStatus, ChangesetComment, \
57 from rhodecode.model.db import (PullRequest, ChangesetStatus, ChangesetComment,
57 Repository
58 Repository, PullRequestVersion)
58 from rhodecode.model.forms import PullRequestForm
59 from rhodecode.model.forms import PullRequestForm
59 from rhodecode.model.meta import Session
60 from rhodecode.model.meta import Session
60 from rhodecode.model.pull_request import PullRequestModel
61 from rhodecode.model.pull_request import PullRequestModel
@@ -675,46 +676,133 b' class PullrequestsController(BaseRepoCon'
675 return redirect(url('my_account_pullrequests'))
676 return redirect(url('my_account_pullrequests'))
676 raise HTTPForbidden()
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 @LoginRequired()
741 @LoginRequired()
679 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
742 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
680 'repository.admin')
743 'repository.admin')
681 def show(self, repo_name, pull_request_id):
744 def show(self, repo_name, pull_request_id):
682 pull_request_id = safe_int(pull_request_id)
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 c.template_context['pull_request_data']['pull_request_id'] = \
753 c.template_context['pull_request_data']['pull_request_id'] = \
686 pull_request_id
754 pull_request_id
687
755
688 # pull_requests repo_name we opened it against
756 # pull_requests repo_name we opened it against
689 # ie. target_repo must match
757 # ie. target_repo must match
690 if repo_name != c.pull_request.target_repo.repo_name:
758 if repo_name != pull_request.target_repo.repo_name:
691 raise HTTPNotFound
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 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
761 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
700 c.pull_request)
762 pull_request)
701 c.allowed_to_delete = PullRequestModel().check_user_delete(
763
702 c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed()
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 cc_model = ChangesetCommentsModel()
793 cc_model = ChangesetCommentsModel()
705
794
706 c.pull_request_reviewers = c.pull_request.reviewers_statuses()
795 c.pull_request_reviewers = pull_request.reviewers_statuses()
707
796
708 c.pull_request_review_status = c.pull_request.calculated_review_status()
797 c.pull_request_review_status = pull_request.calculated_review_status()
709 c.pr_merge_status, c.pr_merge_msg = PullRequestModel().merge_status(
798 c.pr_merge_status, c.pr_merge_msg = PullRequestModel().merge_status(
710 c.pull_request)
799 pull_request)
711 c.approval_msg = None
800 c.approval_msg = None
712 if c.pull_request_review_status != ChangesetStatus.STATUS_APPROVED:
801 if c.pull_request_review_status != ChangesetStatus.STATUS_APPROVED:
713 c.approval_msg = _('Reviewer approval is pending.')
802 c.approval_msg = _('Reviewer approval is pending.')
714 c.pr_merge_status = False
803 c.pr_merge_status = False
715 # load compare data into template context
804 # load compare data into template context
716 enable_comments = not c.pull_request.is_closed()
805 enable_comments = not pull_request.is_closed()
717
718
806
719 # inline comments
807 # inline comments
720 c.inline_comments = cc_model.get_inline_comments(
808 c.inline_comments = cc_model.get_inline_comments(
@@ -725,23 +813,26 b' class PullrequestsController(BaseRepoCon'
725 c.inline_comments, version=at_version)
813 c.inline_comments, version=at_version)
726
814
727 self._load_compare_data(
815 self._load_compare_data(
728 c.pull_request, c.inline_comments, enable_comments=enable_comments)
816 pull_request, c.inline_comments, enable_comments=enable_comments)
729
817
730 # outdated comments
818 # outdated comments
731 c.outdated_comments = {}
819 c.outdated_comments = {}
732 c.outdated_cnt = 0
820 c.outdated_cnt = 0
733 if ChangesetCommentsModel.use_outdated_comments(c.pull_request):
821
822 if ChangesetCommentsModel.use_outdated_comments(pull_request):
734 c.outdated_comments = cc_model.get_outdated_comments(
823 c.outdated_comments = cc_model.get_outdated_comments(
735 c.rhodecode_db_repo.repo_id,
824 c.rhodecode_db_repo.repo_id,
736 pull_request=c.pull_request)
825 pull_request=pull_request)
826
737 # Count outdated comments and check for deleted files
827 # Count outdated comments and check for deleted files
738 for file_name, lines in c.outdated_comments.iteritems():
828 for file_name, lines in c.outdated_comments.iteritems():
739 for comments in lines.values():
829 for comments in lines.values():
830 comments = [comm for comm in comments
831 if comm.outdated_at_version(at_version)]
740 c.outdated_cnt += len(comments)
832 c.outdated_cnt += len(comments)
741 if file_name not in c.included_files:
833 if file_name not in c.included_files:
742 c.deleted_files.append(file_name)
834 c.deleted_files.append(file_name)
743
835
744
745 # this is a hack to properly display links, when creating PR, the
836 # this is a hack to properly display links, when creating PR, the
746 # compare view and others uses different notation, and
837 # compare view and others uses different notation, and
747 # compare_commits.html renders links based on the target_repo.
838 # compare_commits.html renders links based on the target_repo.
@@ -760,6 +851,9 b' class PullrequestsController(BaseRepoCon'
760 c.commit_statuses = statuses
851 c.commit_statuses = statuses
761
852
762 c.ancestor = None # TODO: add ancestor here
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 return render('/pullrequests/pullrequest_show.html')
858 return render('/pullrequests/pullrequest_show.html')
765
859
@@ -813,8 +907,6 b' class PullrequestsController(BaseRepoCon'
813 closing_pr=close_pr
907 closing_pr=close_pr
814 )
908 )
815
909
816
817
818 if allowed_to_change_status:
910 if allowed_to_change_status:
819 old_calculated_status = pull_request.calculated_review_status()
911 old_calculated_status = pull_request.calculated_review_status()
820 # get status if set !
912 # get status if set !
@@ -656,6 +656,16 b' def extract_mentioned_users(s):'
656 return sorted(list(usrs), key=lambda k: k.lower())
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 class AttributeDict(dict):
669 class AttributeDict(dict):
660 def __getattr__(self, attr):
670 def __getattr__(self, attr):
661 return self.get(attr, None)
671 return self.get(attr, None)
@@ -90,7 +90,7 b' class BaseModel(object):'
90 """
90 """
91 Gets instance of given cls using some simple lookup mechanism.
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 :param instance: int or Instance
94 :param instance: int or Instance
95 :param callback: callback to call if all lookups failed
95 :param callback: callback to call if all lookups failed
96 """
96 """
@@ -98,6 +98,9 b' class BaseModel(object):'
98 if isinstance(instance, cls):
98 if isinstance(instance, cls):
99 return instance
99 return instance
100 elif isinstance(instance, (int, long)):
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 return cls.get(instance)
104 return cls.get(instance)
102 else:
105 else:
103 if instance:
106 if instance:
@@ -2933,6 +2933,12 b' class ChangesetComment(Base, BaseModel):'
2933 def outdated(self):
2933 def outdated(self):
2934 return self.display_state == self.COMMENT_OUTDATED
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 def render(self, mentions=False):
2942 def render(self, mentions=False):
2937 from rhodecode.lib import helpers as h
2943 from rhodecode.lib import helpers as h
2938 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2944 return h.render(self.text, renderer=self.renderer, mentions=mentions)
@@ -3117,34 +3123,6 b' class _PullRequestBase(BaseModel):'
3117 else:
3123 else:
3118 return None
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 def get_api_data(self):
3126 def get_api_data(self):
3149 from rhodecode.model.pull_request import PullRequestModel
3127 from rhodecode.model.pull_request import PullRequestModel
3150 pull_request = self
3128 pull_request = self
@@ -3209,6 +3187,35 b' class PullRequest(Base, _PullRequestBase'
3209
3187
3210 return data
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 def __json__(self):
3219 def __json__(self):
3213 return {
3220 return {
3214 'revisions': self.revisions,
3221 'revisions': self.revisions,
@@ -3243,6 +3250,24 b' class PullRequestVersion(Base, _PullRequ'
3243 else:
3250 else:
3244 return '<DB:PullRequestVersion at %#x>' % id(self)
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 class PullRequestReviewers(Base, BaseModel):
3272 class PullRequestReviewers(Base, BaseModel):
3248 __tablename__ = 'pull_request_reviewers'
3273 __tablename__ = 'pull_request_reviewers'
@@ -208,8 +208,14 b' input[type="button"] {'
208 color: @rcdarkblue;
208 color: @rcdarkblue;
209 }
209 }
210
210
211 //disabled buttons
212 //last; overrides any other styles
211 &:disabled {
213 &:disabled {
214 opacity: .7;
215 cursor: auto;
216 background-color: white;
212 color: @grey4;
217 color: @grey4;
218 text-shadow: none;
213 }
219 }
214
220
215 // TODO: johbo: Check if we can avoid this, indicates that the structure
221 // TODO: johbo: Check if we can avoid this, indicates that the structure
@@ -313,7 +313,7 b' table.code-difftable {'
313 // Comments
313 // Comments
314
314
315 div.comment:target {
315 div.comment:target {
316 border-left: 6px solid @comment-highlight-color;
316 border-left: 6px solid @comment-highlight-color !important;
317 padding-left: 3px;
317 padding-left: 3px;
318 margin-left: -9px;
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 .filediff {
749 .filediff {
741 border: 1px solid @grey5;
750 border: 1px solid @grey5;
742
751
@@ -894,6 +903,7 b' input.filediff-collapse-state {'
894 display: none;
903 display: none;
895 }
904 }
896 }
905 }
906
897 .inline-comments {
907 .inline-comments {
898 border-radius: @border-radius;
908 border-radius: @border-radius;
899 background: @grey6;
909 background: @grey6;
@@ -904,6 +914,7 b' input.filediff-collapse-state {'
904 .comment-outdated {
914 .comment-outdated {
905 opacity: 0.5;
915 opacity: 0.5;
906 }
916 }
917
907 .comment-inline {
918 .comment-inline {
908 background: white;
919 background: white;
909 padding: (@comment-padding + 3px) @comment-padding;
920 padding: (@comment-padding + 3px) @comment-padding;
@@ -170,22 +170,6 b' tr.inline-comments div {'
170 color: @rcblue;
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 // Comment Form
173 // Comment Form
190 div.comment-form {
174 div.comment-form {
191 margin-top: 20px;
175 margin-top: 20px;
@@ -1395,9 +1395,7 b' table.integrations {'
1395 width: 92%;
1395 width: 92%;
1396 margin-bottom: 1em;
1396 margin-bottom: 1em;
1397 }
1397 }
1398 #update_commits {
1398
1399 float: right;
1400 }
1401 .compare_view_commits tr{
1399 .compare_view_commits tr{
1402 height: 20px;
1400 height: 20px;
1403 }
1401 }
@@ -48,58 +48,6 b' var tableTr = function(cls, body){'
48 return _el.children[0].children[0].children[0];
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 function bindDeleteCommentButtons() {
51 function bindDeleteCommentButtons() {
104 $('.delete-comment').one('click', function() {
52 $('.delete-comment').one('click', function() {
105 var comment_id = $(this).data("comment-id");
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 var deleteComment = function(comment_id) {
61 var deleteComment = function(comment_id) {
223 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
62 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
224 var postData = {
63 var postData = {
@@ -232,89 +71,6 b' var deleteComment = function(comment_id)'
232 ajaxPOST(url, postData, success);
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 var bindToggleButtons = function() {
75 var bindToggleButtons = function() {
320 $('.comment-toggle').on('click', function() {
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 /* Comment form for main and inline comments */
108 /* Comment form for main and inline comments */
384 var CommentForm = (function() {
109 var CommentForm = (function() {
@@ -679,11 +404,13 b' var CommentsController = function() { /*'
679 var $td = $node.closest('td');
404 var $td = $node.closest('td');
680 $node.closest('.comment-inline-form').removeClass('comment-inline-form-open');
405 $node.closest('.comment-inline-form').removeClass('comment-inline-form-open');
681 return false;
406 return false;
682 }
407 };
408
683 this.getLineNumber = function(node) {
409 this.getLineNumber = function(node) {
684 var $node = $(node);
410 var $node = $(node);
685 return $node.closest('td').attr('data-line-number');
411 return $node.closest('td').attr('data-line-number');
686 }
412 };
413
687 this.scrollToComment = function(node, offset) {
414 this.scrollToComment = function(node, offset) {
688 if (!node) {
415 if (!node) {
689 node = $('.comment-selected');
416 node = $('.comment-selected');
@@ -702,20 +429,23 b' var CommentsController = function() { /*'
702 }
429 }
703 var $next = $('.comment-current').eq(nextIdx);
430 var $next = $('.comment-current').eq(nextIdx);
704 var $cb = $next.closest('.cb');
431 var $cb = $next.closest('.cb');
705 $cb.removeClass('cb-collapsed')
432 $cb.removeClass('cb-collapsed');
706
433
707 var $filediffCollapseState = $cb.closest('.filediff').prev();
434 var $filediffCollapseState = $cb.closest('.filediff').prev();
708 $filediffCollapseState.prop('checked', false);
435 $filediffCollapseState.prop('checked', false);
709 $next.addClass('comment-selected');
436 $next.addClass('comment-selected');
710 scrollToElement($next);
437 scrollToElement($next);
711 return false;
438 return false;
712 }
439 };
440
713 this.nextComment = function(node) {
441 this.nextComment = function(node) {
714 return self.scrollToComment(node, 1);
442 return self.scrollToComment(node, 1);
715 }
443 };
444
716 this.prevComment = function(node) {
445 this.prevComment = function(node) {
717 return self.scrollToComment(node, -1);
446 return self.scrollToComment(node, -1);
718 }
447 };
448
719 this.deleteComment = function(node) {
449 this.deleteComment = function(node) {
720 if (!confirm(_gettext('Delete this comment?'))) {
450 if (!confirm(_gettext('Delete this comment?'))) {
721 return false;
451 return false;
@@ -744,7 +474,8 b' var CommentsController = function() { /*'
744 return false;
474 return false;
745 };
475 };
746 ajaxPOST(url, postData, success, failure);
476 ajaxPOST(url, postData, success, failure);
747 }
477 };
478
748 this.toggleComments = function(node, show) {
479 this.toggleComments = function(node, show) {
749 var $filediff = $(node).closest('.filediff');
480 var $filediff = $(node).closest('.filediff');
750 if (show === true) {
481 if (show === true) {
@@ -757,12 +488,14 b' var CommentsController = function() { /*'
757 $filediff.toggleClass('hide-comments');
488 $filediff.toggleClass('hide-comments');
758 }
489 }
759 return false;
490 return false;
760 }
491 };
492
761 this.toggleLineComments = function(node) {
493 this.toggleLineComments = function(node) {
762 self.toggleComments(node, true);
494 self.toggleComments(node, true);
763 var $node = $(node);
495 var $node = $(node);
764 $node.closest('tr').toggleClass('hide-line-comments');
496 $node.closest('tr').toggleClass('hide-line-comments');
765 }
497 };
498
766 this.createComment = function(node) {
499 this.createComment = function(node) {
767 var $node = $(node);
500 var $node = $(node);
768 var $td = $node.closest('td');
501 var $td = $node.closest('td');
@@ -832,7 +565,6 b' var CommentsController = function() { /*'
832 console.error(e);
565 console.error(e);
833 }
566 }
834
567
835
836 // re trigger the linkification of next/prev navigation
568 // re trigger the linkification of next/prev navigation
837 linkifyComments($('.inline-comment-injected'));
569 linkifyComments($('.inline-comment-injected'));
838 timeagoActivate();
570 timeagoActivate();
@@ -863,7 +595,7 b' var CommentsController = function() { /*'
863 }
595 }
864
596
865 $form.addClass('comment-inline-form-open');
597 $form.addClass('comment-inline-form-open');
866 }
598 };
867
599
868 this.renderInlineComments = function(file_comments) {
600 this.renderInlineComments = function(file_comments) {
869 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
601 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
@@ -892,4 +624,4 b' var CommentsController = function() { /*'
892 firefoxAnchorFix();
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 ## template for inline comment form
179 ## template for inline comment form
180 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
180 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
181 ${comment.comment_inline_form()}
182
181
183 ## ## render comments and inlines
182 ## render comments
184 ${comment.generate_comments()}
183 ${comment.generate_comments()}
185
184
186 ## main comment form and it status
185 ## main comment form and it status
@@ -210,12 +209,12 b''
210 if(button.hasClass("comments-visible")) {
209 if(button.hasClass("comments-visible")) {
211 $('#{0} .inline-comments'.format(boxid)).each(function(index){
210 $('#{0} .inline-comments'.format(boxid)).each(function(index){
212 $(this).hide();
211 $(this).hide();
213 })
212 });
214 button.removeClass("comments-visible");
213 button.removeClass("comments-visible");
215 } else {
214 } else {
216 $('#{0} .inline-comments'.format(boxid)).each(function(index){
215 $('#{0} .inline-comments'.format(boxid)).each(function(index){
217 $(this).show();
216 $(this).show();
218 })
217 });
219 button.addClass("comments-visible");
218 button.addClass("comments-visible");
220 }
219 }
221 });
220 });
@@ -230,7 +229,7 b''
230 url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}',
229 url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}',
231 success: function(data) {
230 success: function(data) {
232 if(data.results.length === 0){
231 if(data.results.length === 0){
233 $('#child_link').html('${_('No Child Commits')}').addClass('disabled');
232 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
234 }
233 }
235 if(data.results.length === 1){
234 if(data.results.length === 1){
236 var commit = data.results[0];
235 var commit = data.results[0];
@@ -263,7 +262,7 b''
263 // >1 links show them to user to choose
262 // >1 links show them to user to choose
264 if(!$('#parent_link').hasClass('disabled')){
263 if(!$('#parent_link').hasClass('disabled')){
265 $.ajax({
264 $.ajax({
266 url: '${h.url('changeset_parents',repo_name=c.repo_name, revision=c.commit.raw_id)}',
265 url: '${h.url("changeset_parents",repo_name=c.repo_name, revision=c.commit.raw_id)}',
267 success: function(data) {
266 success: function(data) {
268 if(data.results.length === 0){
267 if(data.results.length === 0){
269 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
268 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
@@ -6,14 +6,14 b''
6 <%namespace name="base" file="/base/base.html"/>
6 <%namespace name="base" file="/base/base.html"/>
7
7
8 <%def name="comment_block(comment, inline=False)">
8 <%def name="comment_block(comment, inline=False)">
9 <div
9 <div class="comment
10 class="comment
10 ${'comment-inline' if inline else ''}
11 ${'comment-inline' if inline else ''}
11 ${'comment-outdated' if comment.outdated_at_version(getattr(c, 'at_version', None)) else 'comment-current'}"
12 ${'comment-outdated' if comment.outdated else 'comment-current'}"
12 id="comment-${comment.comment_id}"
13 "
13 line="${comment.line_no}"
14 id="comment-${comment.comment_id}"
14 data-comment-id="${comment.comment_id}"
15 line="${comment.line_no}"
15 style="${'display: none;' if comment.outdated_at_version(getattr(c, 'at_version', None)) else ''}">
16 data-comment-id="${comment.comment_id}">
16
17 <div class="meta">
17 <div class="meta">
18 <div class="author">
18 <div class="author">
19 ${base.gravatar_with_user(comment.author.email, 16)}
19 ${base.gravatar_with_user(comment.author.email, 16)}
@@ -22,21 +22,27 b''
22 ${h.age_component(comment.modified_at, time_is_local=True)}
22 ${h.age_component(comment.modified_at, time_is_local=True)}
23 </div>
23 </div>
24 <div class="status-change">
24 <div class="status-change">
25 %if comment.pull_request:
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)}">
26 % if comment.outdated:
27 %if comment.status_change:
27 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
28 ${_('Vote on pull request #%s') % comment.pull_request.pull_request_id}:
28 ${_('Outdated comment from pull request version {}').format(comment.pull_request_version_id)}
29 %else:
29 </a>
30 ${_('Comment on pull request #%s') % comment.pull_request.pull_request_id}
30 % else:
31 %endif
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 </a>
32 %if comment.status_change:
33 %else:
33 ${_('Vote on pull request #%s') % comment.pull_request.pull_request_id}:
34 %if comment.status_change:
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 ${_('Status change on commit')}:
41 ${_('Status change on commit')}:
36 %else:
42 % else:
37 ${_('Comment on commit')}
43 ${_('Comment on commit')}
38 %endif
44 % endif
39 %endif
45 % endif
40 </div>
46 </div>
41 %if comment.status_change:
47 %if comment.status_change:
42 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
48 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
@@ -52,14 +58,19 b''
52 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
58 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
53 ## only super-admin, repo admin OR comment owner can delete
59 ## only super-admin, repo admin OR comment owner can delete
54 %if not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed()):
60 %if not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed()):
61 ## permissions to delete
55 %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
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 ## TODO: dan: add edit comment here
63 ## TODO: dan: add edit comment here
57 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a> |
64 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
58 %if not comment.outdated:
65 %else:
59 <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a> |
66 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
60 <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
61 %endif
62 %endif
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 %endif
74 %endif
64
75
65 </div>
76 </div>
@@ -67,102 +78,9 b''
67 <div class="text">
78 <div class="text">
68 ${comment.render(mentions=True)|n}
79 ${comment.render(mentions=True)|n}
69 </div>
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}">&para;</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 </div>
82 </div>
107 </%def>
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 ## generate main comments
84 ## generate main comments
167 <%def name="generate_comments(include_pull_request=False, is_pull_request=False)">
85 <%def name="generate_comments(include_pull_request=False, is_pull_request=False)">
168 <div id="comments">
86 <div id="comments">
@@ -182,6 +100,7 b''
182
100
183 ## MAIN COMMENT FORM
101 ## MAIN COMMENT FORM
184 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
102 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
103
185 %if is_compare:
104 %if is_compare:
186 <% form_id = "comments_form_compare" %>
105 <% form_id = "comments_form_compare" %>
187 %else:
106 %else:
@@ -394,10 +394,13 b' from rhodecode.lib.diffs import NEW_FILE'
394 %for comment in comments:
394 %for comment in comments:
395 ${commentblock.comment_block(comment, inline=True)}
395 ${commentblock.comment_block(comment, inline=True)}
396 %endfor
396 %endfor
397
397 <span onclick="return Rhodecode.comments.createComment(this)"
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 ${_('Add another comment')}
401 ${_('Add another comment')}
400 </span>
402 </span>
403
401 </div>
404 </div>
402 </%def>
405 </%def>
403
406
@@ -918,9 +918,6 b''
918 $(btns).each(fn_display);
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 var commentTotals = {};
921 var commentTotals = {};
925 $.each(file_comments, function(i, comment) {
922 $.each(file_comments, function(i, comment) {
926 var path = $(comment).attr('path');
923 var path = $(comment).attr('path');
@@ -165,24 +165,62 b''
165 <div>
165 <div>
166 <div class="comments-number">
166 <div class="comments-number">
167 %if c.comments:
167 %if c.comments:
168 <a href="#comments">${ungettext("%d Pull request comment", "%d Pull request comments", len(c.comments)) % len(c.comments)}</a>,
168 <a href="#comments">${ungettext("%d General Comment", "%d General Comments", len(c.comments)) % len(c.comments)}</a>,
169 %else:
169 %else:
170 ${ungettext("%d Pull request comment", "%d Pull request comments", len(c.comments)) % len(c.comments)}
170 ${ungettext("%d General Comment", "%d General Comments", len(c.comments)) % len(c.comments)}
171 %endif
171 %endif
172
172 %if c.inline_cnt:
173 %if c.inline_cnt:
173 <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 <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 %else:
175 %else:
175 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
176 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
176 %endif
177 %endif
177
178
178
179 %if c.outdated_cnt:
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>
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
181 % endif
182 </div>
182 </div>
183 </div>
183 </div>
184 </div>
184 </div>
185
186 </div>
187
188 <div class="field">
189 <div class="label-summary">
190 <label>${_('Versions')}:</label>
185 </div>
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 <div id="pr-save" class="field" style="display: none;">
224 <div id="pr-save" class="field" style="display: none;">
187 <div class="label-summary"></div>
225 <div class="label-summary"></div>
188 <div class="input">
226 <div class="input">
@@ -291,7 +329,9 b''
291 % endif
329 % endif
292 <div class="compare_view_commits_title">
330 <div class="compare_view_commits_title">
293 % if c.allowed_to_update and not c.pull_request.is_closed():
331 % if c.allowed_to_update and not c.pull_request.is_closed():
294 <button id="update_commits" class="btn btn-small">${_('Update commits')}</button>
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 % endif
335 % endif
296 % if len(c.commit_ranges):
336 % if len(c.commit_ranges):
297 <h2>${ungettext('Compare View: %s commit','Compare View: %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}</h2>
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 ${cbdiffs.render_diffset(
345 ${cbdiffs.render_diffset(
306 c.diffset, use_comments=True,
346 c.diffset, use_comments=True,
307 collapse_when_files_over=30,
347 collapse_when_files_over=30,
308 disable_new_comments=c.pull_request.is_closed())}
348 disable_new_comments=not c.allowed_to_comment)}
309
349
310 </div>
350 </div>
311 % endif
351 % endif
@@ -313,9 +353,8 b''
313
353
314 ## template for inline comment form
354 ## template for inline comment form
315 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
355 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
316 ${comment.comment_inline_form()}
317
356
318 ## render comments and inlines
357 ## render general comments
319 ${comment.generate_comments(include_pull_request=True, is_pull_request=True)}
358 ${comment.generate_comments(include_pull_request=True, is_pull_request=True)}
320
359
321 % if not c.pull_request.is_closed():
360 % if not c.pull_request.is_closed():
@@ -394,7 +433,7 b''
394 this.closeButton.hide();
433 this.closeButton.hide();
395 this.addButton.hide();
434 this.addButton.hide();
396 this.removeButtons.css('visibility', 'hidden');
435 this.removeButtons.css('visibility', 'hidden');
397 },
436 }
398 };
437 };
399
438
400 PRDetails.init();
439 PRDetails.init();
@@ -402,7 +441,7 b''
402
441
403 $('#show-outdated-comments').on('click', function(e){
442 $('#show-outdated-comments').on('click', function(e){
404 var button = $(this);
443 var button = $(this);
405 var outdated = $('.outdated');
444 var outdated = $('.comment-outdated');
406 if (button.html() === "(Show)") {
445 if (button.html() === "(Show)") {
407 button.html("(Hide)");
446 button.html("(Hide)");
408 outdated.show();
447 outdated.show();
@@ -428,29 +467,6 b''
428 $(btns).each(fn_display);
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 $('#merge_pull_request_form').submit(function() {
470 $('#merge_pull_request_form').submit(function() {
455 if (!$('#merge_pull_request').attr('disabled')) {
471 if (!$('#merge_pull_request').attr('disabled')) {
456 $('#merge_pull_request').attr('disabled', 'disabled');
472 $('#merge_pull_request').attr('disabled', 'disabled');
General Comments 0
You need to be logged in to leave comments. Login now