##// 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 156 c.ignorews_url = _ignorews_url
157 157 c.context_url = _context_url
158 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 169 # get ranges of commit ids if preset
160 170 commit_range = commit_id_range.split('...')[:2]
161 enable_comments = True
171
162 172 try:
163 173 pre_load = ['affected_files', 'author', 'branch', 'date',
164 174 'message', 'parents']
165 175
166 176 if len(commit_range) == 2:
167 enable_comments = False
168 177 commits = c.rhodecode_repo.get_commits(
169 178 start_id=commit_range[0], end_id=commit_range[1],
170 179 pre_load=pre_load)
@@ -190,60 +199,45 b' class ChangesetController(BaseRepoContro'
190 199 c.lines_deleted = 0
191 200
192 201 c.commit_statuses = ChangesetStatus.STATUSES
193 c.comments = []
194 c.statuses = []
195 202 c.inline_comments = []
196 203 c.inline_cnt = 0
197 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 227 # Iterate over ranges (default commit view is always one commit)
200 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 229 c.changes[commit.raw_id] = []
227 230
228 231 commit2 = commit
229 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 234 _diff = c.rhodecode_repo.get_diff(
236 235 commit1, commit2,
237 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 237 diff_processor = diffs.DiffProcessor(
245 238 _diff, format='newdiff', diff_limit=diff_limit,
246 239 file_limit=file_limit, show_full_diff=fulldiff)
240
247 241 commit_changes = OrderedDict()
248 242 if method == 'show':
249 243 _parsed = diff_processor.prepare()
@@ -259,10 +253,15 b' class ChangesetController(BaseRepoContro'
259 253 return None
260 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 260 diffset = codeblocks.DiffSet(
263 261 repo_name=c.repo_name,
264 262 source_node_getter=_node_getter(commit1),
265 263 target_node_getter=_node_getter(commit2),
264 comments=inline_comments
266 265 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
267 266 c.changes[commit.raw_id] = diffset
268 267 else:
@@ -273,10 +272,6 b' class ChangesetController(BaseRepoContro'
273 272 # sort comments by how they were generated
274 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 276 if len(c.commit_ranges) == 1:
282 277 c.commit = c.commit_ranges[0]
@@ -358,6 +358,7 b' class DiffSet(object):'
358 358 source_nodes=None, target_nodes=None,
359 359 max_file_size_limit=150 * 1024, # files over this size will
360 360 # use fast highlighting
361 comments=None,
361 362 ):
362 363
363 364 self.highlight_mode = highlight_mode
@@ -367,7 +368,7 b' class DiffSet(object):'
367 368 self.source_nodes = source_nodes or {}
368 369 self.target_nodes = target_nodes or {}
369 370 self.repo_name = repo_name
370
371 self.comments = comments or {}
371 372 self.max_file_size_limit = max_file_size_limit
372 373
373 374 def render_patchset(self, patchset, source_ref=None, target_ref=None):
@@ -537,6 +538,8 b' class DiffSet(object):'
537 538 original.lineno = before['old_lineno']
538 539 original.content = before['line']
539 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 544 if after:
542 545 if after['action'] == 'new-no-nl':
@@ -548,6 +551,8 b' class DiffSet(object):'
548 551 modified.lineno = after['new_lineno']
549 552 modified.content = after['line']
550 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 557 # diff the lines
553 558 if before_tokens and after_tokens:
@@ -569,6 +574,20 b' class DiffSet(object):'
569 574
570 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 591 def get_line_tokens(self, line_text, line_number, file=None):
573 592 filenode = None
574 593 filename = None
@@ -619,22 +638,26 b' class DiffSet(object):'
619 638 if line.original:
620 639 if line.original.action == ' ':
621 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 643 continue
624 644
625 645 if line.original.action == '-':
626 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 650 if line.modified.action == '+':
630 651 buf.append((
631 652 None, line.modified.lineno,
632 line.modified.action, line.modified.content))
653 line.modified.action, line.modified.content,
654 line.modified.comments))
633 655 continue
634 656
635 657 if line.modified:
636 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 662 for b in buf:
640 663 yield b
@@ -730,6 +730,7 b' input.filediff-collapse-state {'
730 730 }
731 731 }
732 732 }
733
733 734 .filediff {
734 735 border: 1px solid @grey5;
735 736
@@ -785,12 +786,13 b' input.filediff-collapse-state {'
785 786 .filediff-menu {
786 787 float: right;
787 788
788 a, span {
789 &> a, &> span {
789 790 padding: 5px;
790 791 display: block;
791 792 float: left
792 793 }
793 794 }
795
794 796 .pill {
795 797 &[op="name"] {
796 798 background: none;
@@ -857,7 +859,87 b' input.filediff-collapse-state {'
857 859 .filediff-collapsed .filediff-expand-button {
858 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 943 table.cb {
862 944 width: 100%;
863 945 border-collapse: collapse;
@@ -956,6 +1038,27 b' table.cb {'
956 1038 font-family: @font-family-monospace;
957 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 1064 &.cb-lineno {
@@ -19,6 +19,9 b' a { cursor: pointer; }'
19 19 clear: both;
20 20 }
21 21
22 .js-template { /* mark a template for javascript use */
23 display: none;
24 }
22 25
23 26 .linebreak {
24 27 display: block;
@@ -323,7 +323,7 b' var bindToggleButtons = function() {'
323 323 };
324 324
325 325 var linkifyComments = function(comments) {
326
326 /* TODO: dan: remove this - it should no longer needed */
327 327 for (var i = 0; i < comments.length; i++) {
328 328 var comment_id = $(comments[i]).data('comment-id');
329 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 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 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 329 </script>
118 330 <%include file="/base/plugins_base.html"/>
119 331 <!--[if lt IE 9]>
@@ -147,8 +147,7 b''
147 147 ${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
148 148 %endif
149 149 %if c.inline_cnt:
150 ## this is replaced with a proper link to first comment via JS linkifyComments() func
151 <a href="#inline-comments" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a>
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>
152 151 %else:
153 152 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
154 153 %endif
@@ -171,23 +170,23 b''
171 170 </div><!-- end sidebar -->
172 171 </div> <!-- end summary -->
173 172 <div class="cs_files">
174 ${cbdiffs.render_diffset_menu()}
175
176 <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
177 ${cbdiffs.render_diffset(c.changes[c.commit.raw_id], commit=c.commit)}
173 <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
174 ${cbdiffs.render_diffset_menu()}
175 ${cbdiffs.render_diffset(
176 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True)}
178 177 </div>
179 </div>
180 178
181 179 ## template for inline comment form
182 180 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
183 181 ${comment.comment_inline_form()}
184 182
185 ## render comments and inlines
183 ## ## render comments and inlines
186 184 ${comment.generate_comments()}
187 185
188 186 ## main comment form and it status
189 187 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
190 188 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
189 </div>
191 190
192 191 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
193 192 <script type="text/javascript">
@@ -310,7 +309,6 b''
310 309
311 310 // inject comments into their proper positions
312 311 var file_comments = $('.inline-comment-placeholder');
313 renderInlineComments(file_comments, true);
314 312 })
315 313 </script>
316 314
@@ -6,7 +6,14 b''
6 6 <%namespace name="base" file="/base/base.html"/>
7 7
8 8 <%def name="comment_block(comment, inline=False)">
9 <div 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 17 <div class="meta">
11 18 <div class="author">
12 19 ${base.gravatar_with_user(comment.author.email, 16)}
@@ -46,24 +53,15 b''
46 53 ## only super-admin, repo admin OR comment owner can delete
47 54 %if not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed()):
48 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>
50 %if inline:
51 <div class="comment-links-divider"> | </div>
56 ## TODO: dan: add edit comment here
57 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
58 %if not comment.outdated:
59 <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
60 <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
52 61 %endif
53 62 %endif
54 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 65 </div>
68 66 </div>
69 67 <div class="text">
@@ -165,31 +163,8 b''
165 163 </%def>
166 164
167 165
168 ## generates inlines taken from c.comments var
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
166 ## generate main comments
189 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 168 %for comment in c.comments:
194 169 <div id="comment-tr-${comment.comment_id}">
195 170 ## only render comments that are not from pull request, or from
@@ -54,12 +54,12 b''
54 54 ##CS
55 55 <%include file="../compare/compare_commits.html"/>
56 56 <div class="cs_files">
57 ${cbdiffs.render_diffset_menu()}
58 57 <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
59 58 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
60 59 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
60 ${cbdiffs.render_diffset_menu()}
61 61 %for commit in c.commit_ranges:
62 ${cbdifss.render_diffset(
62 ${cbdiffs.render_diffset(
63 63 diffset=c.changes[commit.raw_id],
64 64 collapse_when_files_over=5,
65 65 commit=commit,
@@ -34,7 +34,74 b" return h.url('', **new_args)"
34 34 # add a ruler at to the output
35 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 106 collapse_all = len(diffset.files) > collapse_when_files_over
40 107 %>
@@ -101,12 +168,12 b' collapse_all = len(diffset.files) > coll'
101 168 <div
102 169 class="filediff"
103 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 172 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
106 173 <div class="filediff-collapse-indicator"></div>
107 174 ${diff_ops(filediff)}
108 175 </label>
109 ${diff_menu(filediff)}
176 ${diff_menu(filediff, use_comments=use_comments)}
110 177 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
111 178 %if not filediff.hunks:
112 179 %for op_id, op_text in filediff['patch']['stats']['ops'].items():
@@ -159,9 +226,9 b' collapse_all = len(diffset.files) > coll'
159 226 </td>
160 227 </tr>
161 228 %if c.diffmode == 'unified':
162 ${render_hunk_lines_unified(hunk)}
229 ${render_hunk_lines_unified(hunk, use_comments=use_comments)}
163 230 %elif c.diffmode == 'sideside':
164 ${render_hunk_lines_sideside(hunk)}
231 ${render_hunk_lines_sideside(hunk, use_comments=use_comments)}
165 232 %else:
166 233 <tr class="cb-line">
167 234 <td>unknown diff mode</td>
@@ -227,7 +294,7 b' from rhodecode.lib.diffs import NEW_FILE'
227 294 %endif
228 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 299 <span class="pill-group" style="float: right">
233 300 %if BIN_FILENODE in stats['ops']:
@@ -250,7 +317,7 b' from rhodecode.lib.diffs import NEW_FILE'
250 317 ${filemode.startswith('100') and filemode[3:] or filemode}
251 318 </%def>
252 319
253 <%def name="diff_menu(filediff)">
320 <%def name="diff_menu(filediff, use_comments=False)">
254 321 <div class="filediff-menu">
255 322 %if filediff.diffset.source_ref:
256 323 %if filediff.patch['operation'] in ['D', 'M']:
@@ -299,12 +366,41 b' from rhodecode.lib.diffs import NEW_FILE'
299 366 >
300 367 ${_('Download diff')}
301 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 384 %endif
303 385 </div>
304 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 404 %for i, line in enumerate(hunk.sideside):
309 405 <%
310 406 old_line_anchor, new_line_anchor = None, None
@@ -326,7 +422,14 b' from rhodecode.lib.diffs import NEW_FILE'
326 422 </td>
327 423 <td class="cb-content ${action_class(line.original.action)}"
328 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 433 </td>
331 434 <td class="cb-lineno ${action_class(line.modified.action)}"
332 435 data-line-number="${line.modified.lineno}"
@@ -341,15 +444,21 b' from rhodecode.lib.diffs import NEW_FILE'
341 444 <td class="cb-content ${action_class(line.modified.action)}"
342 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 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 454 </td>
346 455 </tr>
347 456 %endfor
348 457 </%def>
349 458
350 459
351 <%def name="render_hunk_lines_unified(hunk)">
352 %for old_line_no, new_line_no, action, content in hunk.unified:
460 <%def name="render_hunk_lines_unified(hunk, use_comments=False)">
461 %for old_line_no, new_line_no, action, content, comments in hunk.unified:
353 462 <%
354 463 old_line_anchor, new_line_anchor = None, None
355 464 if old_line_no:
@@ -380,12 +489,25 b' from rhodecode.lib.diffs import NEW_FILE'
380 489 </td>
381 490 <td class="cb-content ${action_class(action)}"
382 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>
384 </td>
492 >
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 501 </tr>
386 502 %endfor
387 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 512 <%def name="render_diffset_menu()">
391 513 <div class="diffset-menu clearinner">
@@ -82,7 +82,7 b' class TestChangesetController(object):'
82 82 response.mustcontain('new file 100644')
83 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 87 def test_commit_range_page_different_ops(self, backend):
88 88 commit_id_range = {
@@ -108,10 +108,13 b' class TestChangesetController(object):'
108 108 # svn is special
109 109 if backend.alias == 'svn':
110 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 114 else:
113 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 119 # files op files
117 120 response.mustcontain('File no longer present at commit: %s' %
@@ -119,7 +122,7 b' class TestChangesetController(object):'
119 122 response.mustcontain('Added docstrings to vcs.cli') # commit msg
120 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 127 def test_combined_compare_commit_page_different_ops(self, backend):
125 128 commit_id_range = {
@@ -109,11 +109,17 b' class TestCommitCommentsController(TestC'
109 109 # test DB
110 110 assert ChangesetComment.query().count() == 1
111 111 assert_comment_links(response, 0, ChangesetComment.query().count())
112 response.mustcontain(
113 '''class="inline-comment-placeholder" '''
114 '''path="vcs/web/simplevcs/views/repository.py" '''
115 '''target_id="vcswebsimplevcsviewsrepositorypy"'''
116 )
112
113 if backend.alias == 'svn':
114 response.mustcontain(
115 '''data-f-path="vcs/commands/summary.py" '''
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 124 assert Notification.query().count() == 1
119 125 assert ChangesetComment.query().count() == 1
@@ -271,7 +277,6 b' def assert_comment_links(response, comme'
271 277 inline_comments) % inline_comments
272 278 if inline_comments:
273 279 response.mustcontain(
274 '<a href="#inline-comments" '
275 'id="inline-comments-counter">%s</a>' % inline_comments_text)
280 'id="inline-comments-counter">%s</' % inline_comments_text)
276 281 else:
277 282 response.mustcontain(inline_comments_text)
General Comments 0
You need to be logged in to leave comments. Login now