##// END OF EJS Templates
diffs: add comments to changeset diffs
dan -
r1143:7bd159d9 default
parent child Browse files
Show More
@@ -156,15 +156,24 b' class ChangesetController(BaseRepoContro'
156 c.ignorews_url = _ignorews_url
156 c.ignorews_url = _ignorews_url
157 c.context_url = _context_url
157 c.context_url = _context_url
158 c.fulldiff = fulldiff = request.GET.get('fulldiff')
158 c.fulldiff = fulldiff = request.GET.get('fulldiff')
159
160 # fetch global flags of ignore ws or context lines
161 context_lcl = get_line_ctx('', request.GET)
162 ign_whitespace_lcl = get_ignore_ws('', request.GET)
163
164 # diff_limit will cut off the whole diff if the limit is applied
165 # otherwise it will just hide the big files from the front-end
166 diff_limit = self.cut_off_limit_diff
167 file_limit = self.cut_off_limit_file
168
159 # get ranges of commit ids if preset
169 # get ranges of commit ids if preset
160 commit_range = commit_id_range.split('...')[:2]
170 commit_range = commit_id_range.split('...')[:2]
161 enable_comments = True
171
162 try:
172 try:
163 pre_load = ['affected_files', 'author', 'branch', 'date',
173 pre_load = ['affected_files', 'author', 'branch', 'date',
164 'message', 'parents']
174 'message', 'parents']
165
175
166 if len(commit_range) == 2:
176 if len(commit_range) == 2:
167 enable_comments = False
168 commits = c.rhodecode_repo.get_commits(
177 commits = c.rhodecode_repo.get_commits(
169 start_id=commit_range[0], end_id=commit_range[1],
178 start_id=commit_range[0], end_id=commit_range[1],
170 pre_load=pre_load)
179 pre_load=pre_load)
@@ -190,60 +199,45 b' class ChangesetController(BaseRepoContro'
190 c.lines_deleted = 0
199 c.lines_deleted = 0
191
200
192 c.commit_statuses = ChangesetStatus.STATUSES
201 c.commit_statuses = ChangesetStatus.STATUSES
193 c.comments = []
194 c.statuses = []
195 c.inline_comments = []
202 c.inline_comments = []
196 c.inline_cnt = 0
203 c.inline_cnt = 0
197 c.files = []
204 c.files = []
198
205
206 c.statuses = []
207 c.comments = []
208 if len(c.commit_ranges) == 1:
209 commit = c.commit_ranges[0]
210 c.comments = ChangesetCommentsModel().get_comments(
211 c.rhodecode_db_repo.repo_id,
212 revision=commit.raw_id)
213 c.statuses.append(ChangesetStatusModel().get_status(
214 c.rhodecode_db_repo.repo_id, commit.raw_id))
215 # comments from PR
216 statuses = ChangesetStatusModel().get_statuses(
217 c.rhodecode_db_repo.repo_id, commit.raw_id,
218 with_revisions=True)
219 prs = set(st.pull_request for st in statuses
220 if st is st.pull_request is not None)
221
222 # from associated statuses, check the pull requests, and
223 # show comments from them
224 for pr in prs:
225 c.comments.extend(pr.comments)
226
199 # Iterate over ranges (default commit view is always one commit)
227 # Iterate over ranges (default commit view is always one commit)
200 for commit in c.commit_ranges:
228 for commit in c.commit_ranges:
201 if method == 'show':
202 c.statuses.extend([ChangesetStatusModel().get_status(
203 c.rhodecode_db_repo.repo_id, commit.raw_id)])
204
205 c.comments.extend(ChangesetCommentsModel().get_comments(
206 c.rhodecode_db_repo.repo_id,
207 revision=commit.raw_id))
208
209 # comments from PR
210 st = ChangesetStatusModel().get_statuses(
211 c.rhodecode_db_repo.repo_id, commit.raw_id,
212 with_revisions=True)
213
214 # from associated statuses, check the pull requests, and
215 # show comments from them
216
217 prs = set(x.pull_request for x in
218 filter(lambda x: x.pull_request is not None, st))
219 for pr in prs:
220 c.comments.extend(pr.comments)
221
222 inlines = ChangesetCommentsModel().get_inline_comments(
223 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
224 c.inline_comments.extend(inlines.iteritems())
225
226 c.changes[commit.raw_id] = []
229 c.changes[commit.raw_id] = []
227
230
228 commit2 = commit
231 commit2 = commit
229 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
232 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
230
233
231 # fetch global flags of ignore ws or context lines
232 context_lcl = get_line_ctx('', request.GET)
233 ign_whitespace_lcl = get_ignore_ws('', request.GET)
234
235 _diff = c.rhodecode_repo.get_diff(
234 _diff = c.rhodecode_repo.get_diff(
236 commit1, commit2,
235 commit1, commit2,
237 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
236 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
238
239 # diff_limit will cut off the whole diff if the limit is applied
240 # otherwise it will just hide the big files from the front-end
241 diff_limit = self.cut_off_limit_diff
242 file_limit = self.cut_off_limit_file
243
244 diff_processor = diffs.DiffProcessor(
237 diff_processor = diffs.DiffProcessor(
245 _diff, format='newdiff', diff_limit=diff_limit,
238 _diff, format='newdiff', diff_limit=diff_limit,
246 file_limit=file_limit, show_full_diff=fulldiff)
239 file_limit=file_limit, show_full_diff=fulldiff)
240
247 commit_changes = OrderedDict()
241 commit_changes = OrderedDict()
248 if method == 'show':
242 if method == 'show':
249 _parsed = diff_processor.prepare()
243 _parsed = diff_processor.prepare()
@@ -259,10 +253,15 b' class ChangesetController(BaseRepoContro'
259 return None
253 return None
260 return get_node
254 return get_node
261
255
256 inline_comments = ChangesetCommentsModel().get_inline_comments(
257 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
258 c.inline_cnt += len(inline_comments)
259
262 diffset = codeblocks.DiffSet(
260 diffset = codeblocks.DiffSet(
263 repo_name=c.repo_name,
261 repo_name=c.repo_name,
264 source_node_getter=_node_getter(commit1),
262 source_node_getter=_node_getter(commit1),
265 target_node_getter=_node_getter(commit2),
263 target_node_getter=_node_getter(commit2),
264 comments=inline_comments
266 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
265 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
267 c.changes[commit.raw_id] = diffset
266 c.changes[commit.raw_id] = diffset
268 else:
267 else:
@@ -273,10 +272,6 b' class ChangesetController(BaseRepoContro'
273 # sort comments by how they were generated
272 # sort comments by how they were generated
274 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
273 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
275
274
276 # count inline comments
277 for __, lines in c.inline_comments:
278 for comments in lines.values():
279 c.inline_cnt += len(comments)
280
275
281 if len(c.commit_ranges) == 1:
276 if len(c.commit_ranges) == 1:
282 c.commit = c.commit_ranges[0]
277 c.commit = c.commit_ranges[0]
@@ -358,6 +358,7 b' class DiffSet(object):'
358 source_nodes=None, target_nodes=None,
358 source_nodes=None, target_nodes=None,
359 max_file_size_limit=150 * 1024, # files over this size will
359 max_file_size_limit=150 * 1024, # files over this size will
360 # use fast highlighting
360 # use fast highlighting
361 comments=None,
361 ):
362 ):
362
363
363 self.highlight_mode = highlight_mode
364 self.highlight_mode = highlight_mode
@@ -367,7 +368,7 b' class DiffSet(object):'
367 self.source_nodes = source_nodes or {}
368 self.source_nodes = source_nodes or {}
368 self.target_nodes = target_nodes or {}
369 self.target_nodes = target_nodes or {}
369 self.repo_name = repo_name
370 self.repo_name = repo_name
370
371 self.comments = comments or {}
371 self.max_file_size_limit = max_file_size_limit
372 self.max_file_size_limit = max_file_size_limit
372
373
373 def render_patchset(self, patchset, source_ref=None, target_ref=None):
374 def render_patchset(self, patchset, source_ref=None, target_ref=None):
@@ -537,6 +538,8 b' class DiffSet(object):'
537 original.lineno = before['old_lineno']
538 original.lineno = before['old_lineno']
538 original.content = before['line']
539 original.content = before['line']
539 original.action = self.action_to_op(before['action'])
540 original.action = self.action_to_op(before['action'])
541 original.comments = self.get_comments_for('old',
542 source_file, before['old_lineno'])
540
543
541 if after:
544 if after:
542 if after['action'] == 'new-no-nl':
545 if after['action'] == 'new-no-nl':
@@ -548,6 +551,8 b' class DiffSet(object):'
548 modified.lineno = after['new_lineno']
551 modified.lineno = after['new_lineno']
549 modified.content = after['line']
552 modified.content = after['line']
550 modified.action = self.action_to_op(after['action'])
553 modified.action = self.action_to_op(after['action'])
554 modified.comments = self.get_comments_for('new',
555 target_file, after['new_lineno'])
551
556
552 # diff the lines
557 # diff the lines
553 if before_tokens and after_tokens:
558 if before_tokens and after_tokens:
@@ -569,6 +574,20 b' class DiffSet(object):'
569
574
570 return lines
575 return lines
571
576
577 def get_comments_for(self, version, file, line_number):
578 if hasattr(file, 'unicode_path'):
579 file = file.unicode_path
580
581 if not isinstance(file, basestring):
582 return None
583
584 line_key = {
585 'old': 'o',
586 'new': 'n',
587 }[version] + str(line_number)
588
589 return self.comments.get(file, {}).get(line_key)
590
572 def get_line_tokens(self, line_text, line_number, file=None):
591 def get_line_tokens(self, line_text, line_number, file=None):
573 filenode = None
592 filenode = None
574 filename = None
593 filename = None
@@ -619,22 +638,26 b' class DiffSet(object):'
619 if line.original:
638 if line.original:
620 if line.original.action == ' ':
639 if line.original.action == ' ':
621 yield (line.original.lineno, line.modified.lineno,
640 yield (line.original.lineno, line.modified.lineno,
622 line.original.action, line.original.content)
641 line.original.action, line.original.content,
642 line.original.comments)
623 continue
643 continue
624
644
625 if line.original.action == '-':
645 if line.original.action == '-':
626 yield (line.original.lineno, None,
646 yield (line.original.lineno, None,
627 line.original.action, line.original.content)
647 line.original.action, line.original.content,
648 line.original.comments)
628
649
629 if line.modified.action == '+':
650 if line.modified.action == '+':
630 buf.append((
651 buf.append((
631 None, line.modified.lineno,
652 None, line.modified.lineno,
632 line.modified.action, line.modified.content))
653 line.modified.action, line.modified.content,
654 line.modified.comments))
633 continue
655 continue
634
656
635 if line.modified:
657 if line.modified:
636 yield (None, line.modified.lineno,
658 yield (None, line.modified.lineno,
637 line.modified.action, line.modified.content)
659 line.modified.action, line.modified.content,
660 line.modified.comments)
638
661
639 for b in buf:
662 for b in buf:
640 yield b
663 yield b
@@ -730,6 +730,7 b' input.filediff-collapse-state {'
730 }
730 }
731 }
731 }
732 }
732 }
733
733 .filediff {
734 .filediff {
734 border: 1px solid @grey5;
735 border: 1px solid @grey5;
735
736
@@ -785,12 +786,13 b' input.filediff-collapse-state {'
785 .filediff-menu {
786 .filediff-menu {
786 float: right;
787 float: right;
787
788
788 a, span {
789 &> a, &> span {
789 padding: 5px;
790 padding: 5px;
790 display: block;
791 display: block;
791 float: left
792 float: left
792 }
793 }
793 }
794 }
795
794 .pill {
796 .pill {
795 &[op="name"] {
797 &[op="name"] {
796 background: none;
798 background: none;
@@ -857,7 +859,87 b' input.filediff-collapse-state {'
857 .filediff-collapsed .filediff-expand-button {
859 .filediff-collapsed .filediff-expand-button {
858 display: inline;
860 display: inline;
859 }
861 }
862
863 @comment-padding: 5px;
864
865 /**** COMMENTS ****/
866
867 .filediff-menu {
868 .show-comment-button {
869 display: none;
870 }
871 }
872 &.hide-comments {
873 .inline-comments {
874 display: none;
875 }
876 .filediff-menu {
877 .show-comment-button {
878 display: inline;
879 }
880 .show-comment-button {
881 display: none;
882 }
883 }
884 }
885 .inline-comments {
886 border-radius: @border-radius;
887 background: @grey6;
888 .comment {
889 margin: 0;
890 border-radius: @border-radius;
891 }
892 .comment-outdated {
893 opacity: 0.5;
894 }
895 .comment-inline {
896 background: white;
897 padding: (@comment-padding + 3px) @comment-padding;
898 border: @comment-padding solid @grey6;
899
900 .text {
901 border: none;
902 }
903 .meta {
904 border-bottom: 1px solid @grey6;
905 padding-bottom: 10px;
906 }
907 }
908 .comment-selected {
909 border-left: 6px solid @comment-highlight-color;
910 }
911 .comment-inline-form {
912 padding: @comment-padding;
913 display: none;
914 }
915 .cb-comment-add-button {
916 margin: @comment-padding;
917 }
918 /* hide add comment button when form is open */
919 .comment-inline-form-open + .cb-comment-add-button {
920 display: none;
921 }
922 .comment-inline-form-open {
923 display: block;
924 }
925 /* hide add comment button when form but no comments */
926 .comment-inline-form:first-child + .cb-comment-add-button {
927 display: none;
928 }
929 /* hide add comment button when no comments or form */
930 .cb-comment-add-button:first-child {
931 display: none;
932 }
933 /* hide add comment button when only comment is being deleted */
934 .comment-deleting:first-child + .cb-comment-add-button {
935 display: none;
936 }
937 }
938 /**** END COMMENTS ****/
939
860 }
940 }
941
942
861 table.cb {
943 table.cb {
862 width: 100%;
944 width: 100%;
863 border-collapse: collapse;
945 border-collapse: collapse;
@@ -956,6 +1038,27 b' table.cb {'
956 font-family: @font-family-monospace;
1038 font-family: @font-family-monospace;
957 word-break: break-word;
1039 word-break: break-word;
958 }
1040 }
1041
1042 &> button.cb-comment-box-opener {
1043 padding: 2px 6px 2px 6px;
1044 margin-left: -20px;
1045 margin-top: -2px;
1046 border-radius: @border-radius;
1047 position: absolute;
1048 display: none;
1049 }
1050 .cb-comment {
1051 margin-top: 10px;
1052 white-space: normal;
1053 }
1054 }
1055 &:hover {
1056 button.cb-comment-box-opener {
1057 display: block;
1058 }
1059 &+ td button.cb-comment-box-opener {
1060 display: block
1061 }
959 }
1062 }
960
1063
961 &.cb-lineno {
1064 &.cb-lineno {
@@ -19,6 +19,9 b' a { cursor: pointer; }'
19 clear: both;
19 clear: both;
20 }
20 }
21
21
22 .js-template { /* mark a template for javascript use */
23 display: none;
24 }
22
25
23 .linebreak {
26 .linebreak {
24 display: block;
27 display: block;
@@ -323,7 +323,7 b' var bindToggleButtons = function() {'
323 };
323 };
324
324
325 var linkifyComments = function(comments) {
325 var linkifyComments = function(comments) {
326
326 /* TODO: dan: remove this - it should no longer needed */
327 for (var i = 0; i < comments.length; i++) {
327 for (var i = 0; i < comments.length; i++) {
328 var comment_id = $(comments[i]).data('comment-id');
328 var comment_id = $(comments[i]).data('comment-id');
329 var prev_comment_id = $(comments[i - 1]).data('comment-id');
329 var prev_comment_id = $(comments[i - 1]).data('comment-id');
@@ -347,7 +347,7 b' var linkifyComments = function(comments)'
347 }
347 }
348
348
349 };
349 };
350
350
351 /**
351 /**
352 * Iterates over all the inlines, and places them inside proper blocks of data
352 * Iterates over all the inlines, and places them inside proper blocks of data
353 */
353 */
@@ -114,6 +114,218 b" c.template_context['visual']['default_re"
114 rhodecode_edition: '${c.rhodecode_edition}'
114 rhodecode_edition: '${c.rhodecode_edition}'
115 }
115 }
116 };
116 };
117
118
119 Rhodecode = (function() {
120 function _Rhodecode() {
121 this.comments = new (function() { /* comments controller */
122 var self = this;
123
124 this.cancelComment = function(node) {
125 var $node = $(node);
126 var $td = $node.closest('td');
127 $node.closest('.comment-inline-form').removeClass('comment-inline-form-open');
128 return false;
129 }
130 this.getLineNumber = function(node) {
131 var $node = $(node);
132 return $node.closest('td').attr('data-line-number');
133 }
134 this.scrollToComment = function(node, offset) {
135 if (!node) {
136 node = $('.comment-selected');
137 if (!node.length) {
138 node = $('comment-current')
139 }
140 }
141 $comment = $(node).closest('.comment-current');
142 $comments = $('.comment-current');
143
144 $('.comment-selected').removeClass('comment-selected');
145
146 var nextIdx = $('.comment-current').index($comment) + offset;
147 if (nextIdx >= $comments.length) {
148 nextIdx = 0;
149 }
150 var $next = $('.comment-current').eq(nextIdx);
151 var $cb = $next.closest('.cb');
152 $cb.removeClass('cb-collapsed')
153
154 var $filediffCollapseState = $cb.closest('.filediff').prev();
155 $filediffCollapseState.prop('checked', false);
156 $next.addClass('comment-selected');
157 scrollToElement($next);
158 return false;
159 }
160 this.nextComment = function(node) {
161 return self.scrollToComment(node, 1);
162 }
163 this.prevComment = function(node) {
164 return self.scrollToComment(node, -1);
165 }
166 this.deleteComment = function(node) {
167 if (!confirm(_gettext('Delete this comment?'))) {
168 return false;
169 }
170 var $node = $(node);
171 var $td = $node.closest('td');
172 var $comment = $node.closest('.comment');
173 var comment_id = $comment.attr('data-comment-id');
174 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
175 var postData = {
176 '_method': 'delete',
177 'csrf_token': CSRF_TOKEN
178 };
179
180 $comment.addClass('comment-deleting');
181 $comment.hide('fast');
182
183 var success = function(response) {
184 $comment.remove();
185 return false;
186 };
187 var failure = function(data, textStatus, xhr) {
188 alert("error processing request: " + textStatus);
189 $comment.show('fast');
190 $comment.removeClass('comment-deleting');
191 return false;
192 };
193 ajaxPOST(url, postData, success, failure);
194 }
195 this.createComment = function(node) {
196 var $node = $(node);
197 var $td = $node.closest('td');
198 var $form = $td.find('.comment-inline-form');
199
200 if (!$form.length) {
201 var tmpl = $('#cb-comment-inline-form-template').html();
202 var f_path = $node.closest('.filediff').attr('data-f-path');
203 var lineno = self.getLineNumber(node);
204 tmpl = tmpl.format(f_path, lineno);
205 $form = $(tmpl);
206
207 var $comments = $td.find('.inline-comments');
208 if (!$comments.length) {
209 $comments = $(
210 $('#cb-comments-inline-container-template').html());
211 $td.append($comments);
212 }
213
214 $td.find('.cb-comment-add-button').before($form);
215
216 var pullRequestId = templateContext.pull_request_data.pull_request_id;
217 var commitId = templateContext.commit_data.commit_id;
218 var _form = $form[0];
219 var commentForm = new CommentForm(_form, commitId, pullRequestId, lineno, false);
220 var cm = commentForm.getCmInstance();
221
222 // set a CUSTOM submit handler for inline comments.
223 commentForm.setHandleFormSubmit(function(o) {
224 var text = commentForm.cm.getValue();
225
226 if (text === "") {
227 return;
228 }
229
230 if (lineno === undefined) {
231 alert('missing line !');
232 return;
233 }
234 if (f_path === undefined) {
235 alert('missing file path !');
236 return;
237 }
238
239 var excludeCancelBtn = false;
240 var submitEvent = true;
241 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
242 commentForm.cm.setOption("readOnly", true);
243 var postData = {
244 'text': text,
245 'f_path': f_path,
246 'line': lineno,
247 'csrf_token': CSRF_TOKEN
248 };
249 var submitSuccessCallback = function(json_data) {
250 $form.remove();
251 console.log(json_data)
252 try {
253 var html = json_data.rendered_text;
254 var lineno = json_data.line_no;
255 var target_id = json_data.target_id;
256
257 $comments.find('.cb-comment-add-button').before(html);
258 console.log(lineno, target_id, $comments);
259
260 } catch (e) {
261 console.error(e);
262 }
263
264
265 // re trigger the linkification of next/prev navigation
266 linkifyComments($('.inline-comment-injected'));
267 timeagoActivate();
268 bindDeleteCommentButtons();
269 commentForm.setActionButtonsDisabled(false);
270
271 };
272 var submitFailCallback = function(){
273 commentForm.resetCommentFormState(text)
274 };
275 commentForm.submitAjaxPOST(
276 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
277 });
278
279 setTimeout(function() {
280 // callbacks
281 if (cm !== undefined) {
282 cm.focus();
283 }
284 }, 10);
285
286 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
287 form: _form,
288 parent: $td[0],
289 lineno: lineno,
290 f_path: f_path}
291 );
292 }
293
294 $form.addClass('comment-inline-form-open');
295 }
296
297 this.renderInlineComments = function(file_comments) {
298 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
299
300 for (var i = 0; i < file_comments.length; i++) {
301 var box = file_comments[i];
302
303 var target_id = $(box).attr('target_id');
304
305 // actually comments with line numbers
306 var comments = box.children;
307
308 for (var j = 0; j < comments.length; j++) {
309 var data = {
310 'rendered_text': comments[j].outerHTML,
311 'line_no': $(comments[j]).attr('line'),
312 'target_id': target_id
313 };
314 }
315 }
316
317 // since order of injection is random, we're now re-iterating
318 // from correct order and filling in links
319 linkifyComments($('.inline-comment-injected'));
320 bindDeleteCommentButtons();
321 firefoxAnchorFix();
322 };
323
324 })();
325 }
326 return new _Rhodecode();
327 })();
328
117 </script>
329 </script>
118 <%include file="/base/plugins_base.html"/>
330 <%include file="/base/plugins_base.html"/>
119 <!--[if lt IE 9]>
331 <!--[if lt IE 9]>
@@ -147,8 +147,7 b''
147 ${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
147 ${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
148 %endif
148 %endif
149 %if c.inline_cnt:
149 %if c.inline_cnt:
150 ## this is replaced with a proper link to first comment via JS linkifyComments() func
150 <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>
151 <a href="#inline-comments" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a>
152 %else:
151 %else:
153 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
152 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
154 %endif
153 %endif
@@ -171,23 +170,23 b''
171 </div><!-- end sidebar -->
170 </div><!-- end sidebar -->
172 </div> <!-- end summary -->
171 </div> <!-- end summary -->
173 <div class="cs_files">
172 <div class="cs_files">
174 ${cbdiffs.render_diffset_menu()}
173 <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
175
174 ${cbdiffs.render_diffset_menu()}
176 <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
175 ${cbdiffs.render_diffset(
177 ${cbdiffs.render_diffset(c.changes[c.commit.raw_id], commit=c.commit)}
176 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True)}
178 </div>
177 </div>
179 </div>
180
178
181 ## template for inline comment form
179 ## template for inline comment form
182 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
180 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
183 ${comment.comment_inline_form()}
181 ${comment.comment_inline_form()}
184
182
185 ## render comments and inlines
183 ## ## render comments and inlines
186 ${comment.generate_comments()}
184 ${comment.generate_comments()}
187
185
188 ## main comment form and it status
186 ## main comment form and it status
189 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
187 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
190 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
188 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
189 </div>
191
190
192 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
191 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
193 <script type="text/javascript">
192 <script type="text/javascript">
@@ -310,7 +309,6 b''
310
309
311 // inject comments into their proper positions
310 // inject comments into their proper positions
312 var file_comments = $('.inline-comment-placeholder');
311 var file_comments = $('.inline-comment-placeholder');
313 renderInlineComments(file_comments, true);
314 })
312 })
315 </script>
313 </script>
316
314
@@ -6,7 +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 class="comment ${'comment-inline' if inline else ''}" id="comment-${comment.comment_id}" line="${comment.line_no}" data-comment-id="${comment.comment_id}">
9 <div
10 class="comment
11 ${'comment-inline' if inline else ''}
12 ${'comment-outdated' if comment.outdated else 'comment-current'}"
13 "
14 id="comment-${comment.comment_id}"
15 line="${comment.line_no}"
16 data-comment-id="${comment.comment_id}">
10 <div class="meta">
17 <div class="meta">
11 <div class="author">
18 <div class="author">
12 ${base.gravatar_with_user(comment.author.email, 16)}
19 ${base.gravatar_with_user(comment.author.email, 16)}
@@ -46,24 +53,15 b''
46 ## only super-admin, repo admin OR comment owner can delete
53 ## only super-admin, repo admin OR comment owner can delete
47 %if not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed()):
54 %if not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed()):
48 %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
55 %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
49 <div onClick="deleteComment(${comment.comment_id})" class="delete-comment"> ${_('Delete')}</div>
56 ## TODO: dan: add edit comment here
50 %if inline:
57 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
51 <div class="comment-links-divider"> | </div>
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>
52 %endif
61 %endif
53 %endif
62 %endif
54 %endif
63 %endif
55
64
56 %if inline:
57
58 <div id="prev_c_${comment.comment_id}" class="comment-previous-link" title="${_('Previous comment')}">
59 <a class="arrow_comment_link disabled"><i class="icon-left"></i></a>
60 </div>
61
62 <div id="next_c_${comment.comment_id}" class="comment-next-link" title="${_('Next comment')}">
63 <a class="arrow_comment_link disabled"><i class="icon-right"></i></a>
64 </div>
65 %endif
66
67 </div>
65 </div>
68 </div>
66 </div>
69 <div class="text">
67 <div class="text">
@@ -165,31 +163,8 b''
165 </%def>
163 </%def>
166
164
167
165
168 ## generates inlines taken from c.comments var
166 ## generate main comments
169 <%def name="inlines(is_pull_request=False)">
170 %if is_pull_request:
171 <h2 id="comments">${ungettext("%d Pull Request Comment", "%d Pull Request Comments", len(c.comments)) % len(c.comments)}</h2>
172 %else:
173 <h2 id="comments">${ungettext("%d Commit Comment", "%d Commit Comments", len(c.comments)) % len(c.comments)}</h2>
174 %endif
175 %for path, lines_comments in c.inline_comments:
176 % for line, comments in lines_comments.iteritems():
177 <div style="display: none;" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
178 ## for each comment in particular line
179 %for comment in comments:
180 ${comment_block(comment, inline=True)}
181 %endfor
182 </div>
183 %endfor
184 %endfor
185
186 </%def>
187
188 ## generate inline comments and the main ones
189 <%def name="generate_comments(include_pull_request=False, is_pull_request=False)">
167 <%def name="generate_comments(include_pull_request=False, is_pull_request=False)">
190 ## generate inlines for this changeset
191 ${inlines(is_pull_request)}
192
193 %for comment in c.comments:
168 %for comment in c.comments:
194 <div id="comment-tr-${comment.comment_id}">
169 <div id="comment-tr-${comment.comment_id}">
195 ## only render comments that are not from pull request, or from
170 ## only render comments that are not from pull request, or from
@@ -54,12 +54,12 b''
54 ##CS
54 ##CS
55 <%include file="../compare/compare_commits.html"/>
55 <%include file="../compare/compare_commits.html"/>
56 <div class="cs_files">
56 <div class="cs_files">
57 ${cbdiffs.render_diffset_menu()}
58 <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
57 <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
59 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
58 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
60 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
59 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
60 ${cbdiffs.render_diffset_menu()}
61 %for commit in c.commit_ranges:
61 %for commit in c.commit_ranges:
62 ${cbdifss.render_diffset(
62 ${cbdiffs.render_diffset(
63 diffset=c.changes[commit.raw_id],
63 diffset=c.changes[commit.raw_id],
64 collapse_when_files_over=5,
64 collapse_when_files_over=5,
65 commit=commit,
65 commit=commit,
@@ -34,7 +34,74 b" return h.url('', **new_args)"
34 # add a ruler at to the output
34 # add a ruler at to the output
35 ruler_at_chars=0,
35 ruler_at_chars=0,
36
36
37 # turn on inline comments
38 use_comments=False,
39
37 )">
40 )">
41
42 %if use_comments:
43 <div id="cb-comments-inline-container-template" class="js-template">
44 ${inline_comments_container([])}
45 </div>
46 <div class="js-template" id="cb-comment-inline-form-template">
47 <div class="comment-inline-form ac">
48 %if c.rhodecode_user.username != h.DEFAULT_USER:
49 ${h.form('#', method='get')}
50 <div id="edit-container_{1}" class="clearfix">
51 <div class="comment-title pull-left">
52 ${_('Create a comment on line {1}.')}
53 </div>
54 <div class="comment-help pull-right">
55 ${(_('Comments parsed using %s syntax with %s support.') % (
56 ('<a href="%s">%s</a>' % (h.url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
57 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
58 )
59 )|n
60 }
61 </div>
62 <div style="clear: both"></div>
63 <textarea id="text_{1}" name="text" class="comment-block-ta ac-input"></textarea>
64 </div>
65 <div id="preview-container_{1}" class="clearfix" style="display: none;">
66 <div class="comment-help">
67 ${_('Comment preview')}
68 </div>
69 <div id="preview-box_{1}" class="preview-box"></div>
70 </div>
71 <div class="comment-footer">
72 <div class="action-buttons">
73 <input type="hidden" name="f_path" value="{0}">
74 <input type="hidden" name="line" value="{1}">
75 <button id="preview-btn_{1}" class="btn btn-secondary">${_('Preview')}</button>
76 <button id="edit-btn_{1}" class="btn btn-secondary" style="display: none;">${_('Edit')}</button>
77 ${h.submit('save', _('Comment'), class_='btn btn-success save-inline-form')}
78 </div>
79 <div class="comment-button">
80 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
81 ${_('Cancel')}
82 </button>
83 </div>
84 ${h.end_form()}
85 </div>
86 %else:
87 ${h.form('', class_='inline-form comment-form-login', method='get')}
88 <div class="pull-left">
89 <div class="comment-help pull-right">
90 ${_('You need to be logged in to comment.')} <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a>
91 </div>
92 </div>
93 <div class="comment-button pull-right">
94 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
95 ${_('Cancel')}
96 </button>
97 </div>
98 <div class="clearfix"></div>
99 ${h.end_form()}
100 %endif
101 </div>
102 </div>
103
104 %endif
38 <%
105 <%
39 collapse_all = len(diffset.files) > collapse_when_files_over
106 collapse_all = len(diffset.files) > collapse_when_files_over
40 %>
107 %>
@@ -101,12 +168,12 b' collapse_all = len(diffset.files) > coll'
101 <div
168 <div
102 class="filediff"
169 class="filediff"
103 data-f-path="${filediff['patch']['filename']}"
170 data-f-path="${filediff['patch']['filename']}"
104 id="a_${h.FID(commit and commit.raw_id or '', filediff['patch']['filename'])}">
171 id="a_${h.FID('', filediff['patch']['filename'])}">
105 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
172 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
106 <div class="filediff-collapse-indicator"></div>
173 <div class="filediff-collapse-indicator"></div>
107 ${diff_ops(filediff)}
174 ${diff_ops(filediff)}
108 </label>
175 </label>
109 ${diff_menu(filediff)}
176 ${diff_menu(filediff, use_comments=use_comments)}
110 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
177 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
111 %if not filediff.hunks:
178 %if not filediff.hunks:
112 %for op_id, op_text in filediff['patch']['stats']['ops'].items():
179 %for op_id, op_text in filediff['patch']['stats']['ops'].items():
@@ -159,9 +226,9 b' collapse_all = len(diffset.files) > coll'
159 </td>
226 </td>
160 </tr>
227 </tr>
161 %if c.diffmode == 'unified':
228 %if c.diffmode == 'unified':
162 ${render_hunk_lines_unified(hunk)}
229 ${render_hunk_lines_unified(hunk, use_comments=use_comments)}
163 %elif c.diffmode == 'sideside':
230 %elif c.diffmode == 'sideside':
164 ${render_hunk_lines_sideside(hunk)}
231 ${render_hunk_lines_sideside(hunk, use_comments=use_comments)}
165 %else:
232 %else:
166 <tr class="cb-line">
233 <tr class="cb-line">
167 <td>unknown diff mode</td>
234 <td>unknown diff mode</td>
@@ -227,7 +294,7 b' from rhodecode.lib.diffs import NEW_FILE'
227 %endif
294 %endif
228 </span>
295 </span>
229
296
230 <a class="pill filediff-anchor" href="#a_${h.FID(commit and commit.raw_id or '', filediff.patch['filename'])}"></a>
297 <a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}"></a>
231
298
232 <span class="pill-group" style="float: right">
299 <span class="pill-group" style="float: right">
233 %if BIN_FILENODE in stats['ops']:
300 %if BIN_FILENODE in stats['ops']:
@@ -250,7 +317,7 b' from rhodecode.lib.diffs import NEW_FILE'
250 ${filemode.startswith('100') and filemode[3:] or filemode}
317 ${filemode.startswith('100') and filemode[3:] or filemode}
251 </%def>
318 </%def>
252
319
253 <%def name="diff_menu(filediff)">
320 <%def name="diff_menu(filediff, use_comments=False)">
254 <div class="filediff-menu">
321 <div class="filediff-menu">
255 %if filediff.diffset.source_ref:
322 %if filediff.diffset.source_ref:
256 %if filediff.patch['operation'] in ['D', 'M']:
323 %if filediff.patch['operation'] in ['D', 'M']:
@@ -299,12 +366,41 b' from rhodecode.lib.diffs import NEW_FILE'
299 >
366 >
300 ${_('Download diff')}
367 ${_('Download diff')}
301 </a>
368 </a>
369
370 ## TODO: dan: refactor ignorews_url and context_url into the diff renderer same as diffmode=unified/sideside. Also use ajax to load more context (by clicking hunks)
371 %if hasattr(c, 'ignorews_url'):
372 ${c.ignorews_url(request.GET, h.FID('', filediff['patch']['filename']))}
373 %endif
374 %if hasattr(c, 'context_url'):
375 ${c.context_url(request.GET, h.FID('', filediff['patch']['filename']))}
376 %endif
377
378
379 %if use_comments:
380 <a href="#" onclick="$(this).closest('.filediff').toggleClass('hide-comments'); return false;">
381 <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
382 </a>
383 %endif
302 %endif
384 %endif
303 </div>
385 </div>
304 </%def>
386 </%def>
305
387
306
388
307 <%def name="render_hunk_lines_sideside(hunk)">
389 <%namespace name="commentblock" file="/changeset/changeset_file_comment.html"/>
390 <%def name="inline_comments_container(comments)">
391 <div class="inline-comments">
392 %for comment in comments:
393 ${commentblock.comment_block(comment, inline=True)}
394 %endfor
395 <span onclick="return Rhodecode.comments.createComment(this)"
396 class="btn btn-secondary cb-comment-add-button">
397 ${_('Add another comment')}
398 </span>
399 </div>
400 </%def>
401
402
403 <%def name="render_hunk_lines_sideside(hunk, use_comments=False)">
308 %for i, line in enumerate(hunk.sideside):
404 %for i, line in enumerate(hunk.sideside):
309 <%
405 <%
310 old_line_anchor, new_line_anchor = None, None
406 old_line_anchor, new_line_anchor = None, None
@@ -326,7 +422,14 b' from rhodecode.lib.diffs import NEW_FILE'
326 </td>
422 </td>
327 <td class="cb-content ${action_class(line.original.action)}"
423 <td class="cb-content ${action_class(line.original.action)}"
328 data-line-number="o${line.original.lineno}"
424 data-line-number="o${line.original.lineno}"
329 ><span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span>
425 >
426 %if use_comments and line.original.lineno:
427 ${render_add_comment_button()}
428 %endif
429 <span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span>
430 %if use_comments and line.original.lineno and line.original.comments:
431 ${inline_comments_container(line.original.comments)}
432 %endif
330 </td>
433 </td>
331 <td class="cb-lineno ${action_class(line.modified.action)}"
434 <td class="cb-lineno ${action_class(line.modified.action)}"
332 data-line-number="${line.modified.lineno}"
435 data-line-number="${line.modified.lineno}"
@@ -341,15 +444,21 b' from rhodecode.lib.diffs import NEW_FILE'
341 <td class="cb-content ${action_class(line.modified.action)}"
444 <td class="cb-content ${action_class(line.modified.action)}"
342 data-line-number="n${line.modified.lineno}"
445 data-line-number="n${line.modified.lineno}"
343 >
446 >
447 %if use_comments and line.modified.lineno:
448 ${render_add_comment_button()}
449 %endif
344 <span class="cb-code">${line.modified.action} ${line.modified.content or '' | n}</span>
450 <span class="cb-code">${line.modified.action} ${line.modified.content or '' | n}</span>
451 %if use_comments and line.modified.lineno and line.modified.comments:
452 ${inline_comments_container(line.modified.comments)}
453 %endif
345 </td>
454 </td>
346 </tr>
455 </tr>
347 %endfor
456 %endfor
348 </%def>
457 </%def>
349
458
350
459
351 <%def name="render_hunk_lines_unified(hunk)">
460 <%def name="render_hunk_lines_unified(hunk, use_comments=False)">
352 %for old_line_no, new_line_no, action, content in hunk.unified:
461 %for old_line_no, new_line_no, action, content, comments in hunk.unified:
353 <%
462 <%
354 old_line_anchor, new_line_anchor = None, None
463 old_line_anchor, new_line_anchor = None, None
355 if old_line_no:
464 if old_line_no:
@@ -380,12 +489,25 b' from rhodecode.lib.diffs import NEW_FILE'
380 </td>
489 </td>
381 <td class="cb-content ${action_class(action)}"
490 <td class="cb-content ${action_class(action)}"
382 data-line-number="${new_line_no and 'n' or 'o'}${new_line_no or old_line_no}"
491 data-line-number="${new_line_no and 'n' or 'o'}${new_line_no or old_line_no}"
383 ><span class="cb-code">${action} ${content or '' | n}</span>
492 >
384 </td>
493 %if use_comments:
494 ${render_add_comment_button()}
495 %endif
496 <span class="cb-code">${action} ${content or '' | n}</span>
497 %if use_comments and comments:
498 ${inline_comments_container(comments)}
499 %endif
500 </td>
385 </tr>
501 </tr>
386 %endfor
502 %endfor
387 </%def>
503 </%def>
388
504
505 <%def name="render_add_comment_button()">
506 <button
507 class="btn btn-small btn-primary cb-comment-box-opener"
508 onclick="return Rhodecode.comments.createComment(this)"
509 >+</button>
510 </%def>
389
511
390 <%def name="render_diffset_menu()">
512 <%def name="render_diffset_menu()">
391 <div class="diffset-menu clearinner">
513 <div class="diffset-menu clearinner">
@@ -82,7 +82,7 b' class TestChangesetController(object):'
82 response.mustcontain('new file 100644')
82 response.mustcontain('new file 100644')
83 response.mustcontain('Changed theme to ADC theme') # commit msg
83 response.mustcontain('Changed theme to ADC theme') # commit msg
84
84
85 self._check_diff_menus(response, right_menu=True)
85 self._check_new_diff_menus(response, right_menu=True)
86
86
87 def test_commit_range_page_different_ops(self, backend):
87 def test_commit_range_page_different_ops(self, backend):
88 commit_id_range = {
88 commit_id_range = {
@@ -108,10 +108,13 b' class TestChangesetController(object):'
108 # svn is special
108 # svn is special
109 if backend.alias == 'svn':
109 if backend.alias == 'svn':
110 response.mustcontain('new file 10644')
110 response.mustcontain('new file 10644')
111 response.mustcontain('34 files changed: 1184 inserted, 311 deleted')
111 response.mustcontain('1 file changed: 5 inserted, 1 deleted')
112 response.mustcontain('12 files changed: 236 inserted, 22 deleted')
113 response.mustcontain('21 files changed: 943 inserted, 288 deleted')
112 else:
114 else:
113 response.mustcontain('new file 100644')
115 response.mustcontain('new file 100644')
114 response.mustcontain('33 files changed: 1165 inserted, 308 deleted')
116 response.mustcontain('12 files changed: 222 inserted, 20 deleted')
117 response.mustcontain('21 files changed: 943 inserted, 288 deleted')
115
118
116 # files op files
119 # files op files
117 response.mustcontain('File no longer present at commit: %s' %
120 response.mustcontain('File no longer present at commit: %s' %
@@ -119,7 +122,7 b' class TestChangesetController(object):'
119 response.mustcontain('Added docstrings to vcs.cli') # commit msg
122 response.mustcontain('Added docstrings to vcs.cli') # commit msg
120 response.mustcontain('Changed theme to ADC theme') # commit msg
123 response.mustcontain('Changed theme to ADC theme') # commit msg
121
124
122 self._check_diff_menus(response)
125 self._check_new_diff_menus(response)
123
126
124 def test_combined_compare_commit_page_different_ops(self, backend):
127 def test_combined_compare_commit_page_different_ops(self, backend):
125 commit_id_range = {
128 commit_id_range = {
@@ -109,11 +109,17 b' class TestCommitCommentsController(TestC'
109 # test DB
109 # test DB
110 assert ChangesetComment.query().count() == 1
110 assert ChangesetComment.query().count() == 1
111 assert_comment_links(response, 0, ChangesetComment.query().count())
111 assert_comment_links(response, 0, ChangesetComment.query().count())
112 response.mustcontain(
112
113 '''class="inline-comment-placeholder" '''
113 if backend.alias == 'svn':
114 '''path="vcs/web/simplevcs/views/repository.py" '''
114 response.mustcontain(
115 '''target_id="vcswebsimplevcsviewsrepositorypy"'''
115 '''data-f-path="vcs/commands/summary.py" '''
116 )
116 '''id="a_c--ad05457a43f8"'''
117 )
118 else:
119 response.mustcontain(
120 '''data-f-path="vcs/backends/hg.py" '''
121 '''id="a_c--9c390eb52cd6"'''
122 )
117
123
118 assert Notification.query().count() == 1
124 assert Notification.query().count() == 1
119 assert ChangesetComment.query().count() == 1
125 assert ChangesetComment.query().count() == 1
@@ -271,7 +277,6 b' def assert_comment_links(response, comme'
271 inline_comments) % inline_comments
277 inline_comments) % inline_comments
272 if inline_comments:
278 if inline_comments:
273 response.mustcontain(
279 response.mustcontain(
274 '<a href="#inline-comments" '
280 'id="inline-comments-counter">%s</' % inline_comments_text)
275 'id="inline-comments-counter">%s</a>' % inline_comments_text)
276 else:
281 else:
277 response.mustcontain(inline_comments_text)
282 response.mustcontain(inline_comments_text)
General Comments 0
You need to be logged in to leave comments. Login now