diffs: add comments to changeset diffs

This commit is contained in:
Daniel Dourvaris 2016-10-28 10:39:55 +03:00
parent d1a7921cfc
commit e97cb5ad1e
12 changed files with 563 additions and 124 deletions

View file

@ -156,15 +156,24 @@ class ChangesetController(BaseRepoController):
c.ignorews_url = _ignorews_url
c.context_url = _context_url
c.fulldiff = fulldiff = request.GET.get('fulldiff')
# fetch global flags of ignore ws or context lines
context_lcl = get_line_ctx('', request.GET)
ign_whitespace_lcl = get_ignore_ws('', request.GET)
# diff_limit will cut off the whole diff if the limit is applied
# otherwise it will just hide the big files from the front-end
diff_limit = self.cut_off_limit_diff
file_limit = self.cut_off_limit_file
# get ranges of commit ids if preset
commit_range = commit_id_range.split('...')[:2]
enable_comments = True
try:
pre_load = ['affected_files', 'author', 'branch', 'date',
'message', 'parents']
if len(commit_range) == 2:
enable_comments = False
commits = c.rhodecode_repo.get_commits(
start_id=commit_range[0], end_id=commit_range[1],
pre_load=pre_load)
@ -190,60 +199,45 @@ class ChangesetController(BaseRepoController):
c.lines_deleted = 0
c.commit_statuses = ChangesetStatus.STATUSES
c.comments = []
c.statuses = []
c.inline_comments = []
c.inline_cnt = 0
c.files = []
c.statuses = []
c.comments = []
if len(c.commit_ranges) == 1:
commit = c.commit_ranges[0]
c.comments = ChangesetCommentsModel().get_comments(
c.rhodecode_db_repo.repo_id,
revision=commit.raw_id)
c.statuses.append(ChangesetStatusModel().get_status(
c.rhodecode_db_repo.repo_id, commit.raw_id))
# comments from PR
statuses = ChangesetStatusModel().get_statuses(
c.rhodecode_db_repo.repo_id, commit.raw_id,
with_revisions=True)
prs = set(st.pull_request for st in statuses
if st is st.pull_request is not None)
# from associated statuses, check the pull requests, and
# show comments from them
for pr in prs:
c.comments.extend(pr.comments)
# Iterate over ranges (default commit view is always one commit)
for commit in c.commit_ranges:
if method == 'show':
c.statuses.extend([ChangesetStatusModel().get_status(
c.rhodecode_db_repo.repo_id, commit.raw_id)])
c.comments.extend(ChangesetCommentsModel().get_comments(
c.rhodecode_db_repo.repo_id,
revision=commit.raw_id))
# comments from PR
st = ChangesetStatusModel().get_statuses(
c.rhodecode_db_repo.repo_id, commit.raw_id,
with_revisions=True)
# from associated statuses, check the pull requests, and
# show comments from them
prs = set(x.pull_request for x in
filter(lambda x: x.pull_request is not None, st))
for pr in prs:
c.comments.extend(pr.comments)
inlines = ChangesetCommentsModel().get_inline_comments(
c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
c.inline_comments.extend(inlines.iteritems())
c.changes[commit.raw_id] = []
commit2 = commit
commit1 = commit.parents[0] if commit.parents else EmptyCommit()
# fetch global flags of ignore ws or context lines
context_lcl = get_line_ctx('', request.GET)
ign_whitespace_lcl = get_ignore_ws('', request.GET)
_diff = c.rhodecode_repo.get_diff(
commit1, commit2,
ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
# diff_limit will cut off the whole diff if the limit is applied
# otherwise it will just hide the big files from the front-end
diff_limit = self.cut_off_limit_diff
file_limit = self.cut_off_limit_file
diff_processor = diffs.DiffProcessor(
_diff, format='newdiff', diff_limit=diff_limit,
file_limit=file_limit, show_full_diff=fulldiff)
commit_changes = OrderedDict()
if method == 'show':
_parsed = diff_processor.prepare()
@ -259,10 +253,15 @@ class ChangesetController(BaseRepoController):
return None
return get_node
inline_comments = ChangesetCommentsModel().get_inline_comments(
c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
c.inline_cnt += len(inline_comments)
diffset = codeblocks.DiffSet(
repo_name=c.repo_name,
source_node_getter=_node_getter(commit1),
target_node_getter=_node_getter(commit2),
comments=inline_comments
).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
c.changes[commit.raw_id] = diffset
else:
@ -273,10 +272,6 @@ class ChangesetController(BaseRepoController):
# sort comments by how they were generated
c.comments = sorted(c.comments, key=lambda x: x.comment_id)
# count inline comments
for __, lines in c.inline_comments:
for comments in lines.values():
c.inline_cnt += len(comments)
if len(c.commit_ranges) == 1:
c.commit = c.commit_ranges[0]

View file

@ -358,6 +358,7 @@ class DiffSet(object):
source_nodes=None, target_nodes=None,
max_file_size_limit=150 * 1024, # files over this size will
# use fast highlighting
comments=None,
):
self.highlight_mode = highlight_mode
@ -367,7 +368,7 @@ class DiffSet(object):
self.source_nodes = source_nodes or {}
self.target_nodes = target_nodes or {}
self.repo_name = repo_name
self.comments = comments or {}
self.max_file_size_limit = max_file_size_limit
def render_patchset(self, patchset, source_ref=None, target_ref=None):
@ -537,6 +538,8 @@ class DiffSet(object):
original.lineno = before['old_lineno']
original.content = before['line']
original.action = self.action_to_op(before['action'])
original.comments = self.get_comments_for('old',
source_file, before['old_lineno'])
if after:
if after['action'] == 'new-no-nl':
@ -548,6 +551,8 @@ class DiffSet(object):
modified.lineno = after['new_lineno']
modified.content = after['line']
modified.action = self.action_to_op(after['action'])
modified.comments = self.get_comments_for('new',
target_file, after['new_lineno'])
# diff the lines
if before_tokens and after_tokens:
@ -569,6 +574,20 @@ class DiffSet(object):
return lines
def get_comments_for(self, version, file, line_number):
if hasattr(file, 'unicode_path'):
file = file.unicode_path
if not isinstance(file, basestring):
return None
line_key = {
'old': 'o',
'new': 'n',
}[version] + str(line_number)
return self.comments.get(file, {}).get(line_key)
def get_line_tokens(self, line_text, line_number, file=None):
filenode = None
filename = None
@ -619,22 +638,26 @@ class DiffSet(object):
if line.original:
if line.original.action == ' ':
yield (line.original.lineno, line.modified.lineno,
line.original.action, line.original.content)
line.original.action, line.original.content,
line.original.comments)
continue
if line.original.action == '-':
yield (line.original.lineno, None,
line.original.action, line.original.content)
line.original.action, line.original.content,
line.original.comments)
if line.modified.action == '+':
buf.append((
None, line.modified.lineno,
line.modified.action, line.modified.content))
line.modified.action, line.modified.content,
line.modified.comments))
continue
if line.modified:
yield (None, line.modified.lineno,
line.modified.action, line.modified.content)
line.modified.action, line.modified.content,
line.modified.comments)
for b in buf:
yield b

View file

@ -730,6 +730,7 @@ input.filediff-collapse-state {
}
}
}
.filediff {
border: 1px solid @grey5;
@ -785,12 +786,13 @@ input.filediff-collapse-state {
.filediff-menu {
float: right;
a, span {
&> a, &> span {
padding: 5px;
display: block;
float: left
}
}
.pill {
&[op="name"] {
background: none;
@ -857,7 +859,87 @@ input.filediff-collapse-state {
.filediff-collapsed .filediff-expand-button {
display: inline;
}
@comment-padding: 5px;
/**** COMMENTS ****/
.filediff-menu {
.show-comment-button {
display: none;
}
}
&.hide-comments {
.inline-comments {
display: none;
}
.filediff-menu {
.show-comment-button {
display: inline;
}
.show-comment-button {
display: none;
}
}
}
.inline-comments {
border-radius: @border-radius;
background: @grey6;
.comment {
margin: 0;
border-radius: @border-radius;
}
.comment-outdated {
opacity: 0.5;
}
.comment-inline {
background: white;
padding: (@comment-padding + 3px) @comment-padding;
border: @comment-padding solid @grey6;
.text {
border: none;
}
.meta {
border-bottom: 1px solid @grey6;
padding-bottom: 10px;
}
}
.comment-selected {
border-left: 6px solid @comment-highlight-color;
}
.comment-inline-form {
padding: @comment-padding;
display: none;
}
.cb-comment-add-button {
margin: @comment-padding;
}
/* hide add comment button when form is open */
.comment-inline-form-open + .cb-comment-add-button {
display: none;
}
.comment-inline-form-open {
display: block;
}
/* hide add comment button when form but no comments */
.comment-inline-form:first-child + .cb-comment-add-button {
display: none;
}
/* hide add comment button when no comments or form */
.cb-comment-add-button:first-child {
display: none;
}
/* hide add comment button when only comment is being deleted */
.comment-deleting:first-child + .cb-comment-add-button {
display: none;
}
}
/**** END COMMENTS ****/
}
table.cb {
width: 100%;
border-collapse: collapse;
@ -956,6 +1038,27 @@ table.cb {
font-family: @font-family-monospace;
word-break: break-word;
}
&> button.cb-comment-box-opener {
padding: 2px 6px 2px 6px;
margin-left: -20px;
margin-top: -2px;
border-radius: @border-radius;
position: absolute;
display: none;
}
.cb-comment {
margin-top: 10px;
white-space: normal;
}
}
&:hover {
button.cb-comment-box-opener {
display: block;
}
&+ td button.cb-comment-box-opener {
display: block
}
}
&.cb-lineno {

View file

@ -19,6 +19,9 @@ a { cursor: pointer; }
clear: both;
}
.js-template { /* mark a template for javascript use */
display: none;
}
.linebreak {
display: block;

View file

@ -323,7 +323,7 @@ var bindToggleButtons = function() {
};
var linkifyComments = function(comments) {
/* TODO: dan: remove this - it should no longer needed */
for (var i = 0; i < comments.length; i++) {
var comment_id = $(comments[i]).data('comment-id');
var prev_comment_id = $(comments[i - 1]).data('comment-id');
@ -347,7 +347,7 @@ var linkifyComments = function(comments) {
}
};
/**
* Iterates over all the inlines, and places them inside proper blocks of data
*/

View file

@ -114,6 +114,218 @@ c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default
rhodecode_edition: '${c.rhodecode_edition}'
}
};
Rhodecode = (function() {
function _Rhodecode() {
this.comments = new (function() { /* comments controller */
var self = this;
this.cancelComment = function(node) {
var $node = $(node);
var $td = $node.closest('td');
$node.closest('.comment-inline-form').removeClass('comment-inline-form-open');
return false;
}
this.getLineNumber = function(node) {
var $node = $(node);
return $node.closest('td').attr('data-line-number');
}
this.scrollToComment = function(node, offset) {
if (!node) {
node = $('.comment-selected');
if (!node.length) {
node = $('comment-current')
}
}
$comment = $(node).closest('.comment-current');
$comments = $('.comment-current');
$('.comment-selected').removeClass('comment-selected');
var nextIdx = $('.comment-current').index($comment) + offset;
if (nextIdx >= $comments.length) {
nextIdx = 0;
}
var $next = $('.comment-current').eq(nextIdx);
var $cb = $next.closest('.cb');
$cb.removeClass('cb-collapsed')
var $filediffCollapseState = $cb.closest('.filediff').prev();
$filediffCollapseState.prop('checked', false);
$next.addClass('comment-selected');
scrollToElement($next);
return false;
}
this.nextComment = function(node) {
return self.scrollToComment(node, 1);
}
this.prevComment = function(node) {
return self.scrollToComment(node, -1);
}
this.deleteComment = function(node) {
if (!confirm(_gettext('Delete this comment?'))) {
return false;
}
var $node = $(node);
var $td = $node.closest('td');
var $comment = $node.closest('.comment');
var comment_id = $comment.attr('data-comment-id');
var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
var postData = {
'_method': 'delete',
'csrf_token': CSRF_TOKEN
};
$comment.addClass('comment-deleting');
$comment.hide('fast');
var success = function(response) {
$comment.remove();
return false;
};
var failure = function(data, textStatus, xhr) {
alert("error processing request: " + textStatus);
$comment.show('fast');
$comment.removeClass('comment-deleting');
return false;
};
ajaxPOST(url, postData, success, failure);
}
this.createComment = function(node) {
var $node = $(node);
var $td = $node.closest('td');
var $form = $td.find('.comment-inline-form');
if (!$form.length) {
var tmpl = $('#cb-comment-inline-form-template').html();
var f_path = $node.closest('.filediff').attr('data-f-path');
var lineno = self.getLineNumber(node);
tmpl = tmpl.format(f_path, lineno);
$form = $(tmpl);
var $comments = $td.find('.inline-comments');
if (!$comments.length) {
$comments = $(
$('#cb-comments-inline-container-template').html());
$td.append($comments);
}
$td.find('.cb-comment-add-button').before($form);
var pullRequestId = templateContext.pull_request_data.pull_request_id;
var commitId = templateContext.commit_data.commit_id;
var _form = $form[0];
var commentForm = new CommentForm(_form, commitId, pullRequestId, lineno, false);
var cm = commentForm.getCmInstance();
// set a CUSTOM submit handler for inline comments.
commentForm.setHandleFormSubmit(function(o) {
var text = commentForm.cm.getValue();
if (text === "") {
return;
}
if (lineno === undefined) {
alert('missing line !');
return;
}
if (f_path === undefined) {
alert('missing file path !');
return;
}
var excludeCancelBtn = false;
var submitEvent = true;
commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
commentForm.cm.setOption("readOnly", true);
var postData = {
'text': text,
'f_path': f_path,
'line': lineno,
'csrf_token': CSRF_TOKEN
};
var submitSuccessCallback = function(json_data) {
$form.remove();
console.log(json_data)
try {
var html = json_data.rendered_text;
var lineno = json_data.line_no;
var target_id = json_data.target_id;
$comments.find('.cb-comment-add-button').before(html);
console.log(lineno, target_id, $comments);
} catch (e) {
console.error(e);
}
// re trigger the linkification of next/prev navigation
linkifyComments($('.inline-comment-injected'));
timeagoActivate();
bindDeleteCommentButtons();
commentForm.setActionButtonsDisabled(false);
};
var submitFailCallback = function(){
commentForm.resetCommentFormState(text)
};
commentForm.submitAjaxPOST(
commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
});
setTimeout(function() {
// callbacks
if (cm !== undefined) {
cm.focus();
}
}, 10);
$.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
form: _form,
parent: $td[0],
lineno: lineno,
f_path: f_path}
);
}
$form.addClass('comment-inline-form-open');
}
this.renderInlineComments = function(file_comments) {
show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
for (var i = 0; i < file_comments.length; i++) {
var box = file_comments[i];
var target_id = $(box).attr('target_id');
// actually comments with line numbers
var comments = box.children;
for (var j = 0; j < comments.length; j++) {
var data = {
'rendered_text': comments[j].outerHTML,
'line_no': $(comments[j]).attr('line'),
'target_id': target_id
};
}
}
// since order of injection is random, we're now re-iterating
// from correct order and filling in links
linkifyComments($('.inline-comment-injected'));
bindDeleteCommentButtons();
firefoxAnchorFix();
};
})();
}
return new _Rhodecode();
})();
</script>
<%include file="/base/plugins_base.html"/>
<!--[if lt IE 9]>

View file

@ -147,8 +147,7 @@
${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
%endif
%if c.inline_cnt:
## this is replaced with a proper link to first comment via JS linkifyComments() func
<a href="#inline-comments" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a>
<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>
%else:
${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
%endif
@ -171,23 +170,23 @@
</div><!-- end sidebar -->
</div> <!-- end summary -->
<div class="cs_files">
${cbdiffs.render_diffset_menu()}
<%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
${cbdiffs.render_diffset(c.changes[c.commit.raw_id], commit=c.commit)}
<%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
${cbdiffs.render_diffset_menu()}
${cbdiffs.render_diffset(
c.changes[c.commit.raw_id], commit=c.commit, use_comments=True)}
</div>
</div>
## template for inline comment form
<%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
${comment.comment_inline_form()}
## render comments and inlines
## ## render comments and inlines
${comment.generate_comments()}
## main comment form and it status
${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
</div>
## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
<script type="text/javascript">
@ -310,7 +309,6 @@
// inject comments into their proper positions
var file_comments = $('.inline-comment-placeholder');
renderInlineComments(file_comments, true);
})
</script>

View file

@ -6,7 +6,14 @@
<%namespace name="base" file="/base/base.html"/>
<%def name="comment_block(comment, inline=False)">
<div class="comment ${'comment-inline' if inline else ''}" id="comment-${comment.comment_id}" line="${comment.line_no}" data-comment-id="${comment.comment_id}">
<div
class="comment
${'comment-inline' if inline else ''}
${'comment-outdated' if comment.outdated else 'comment-current'}"
"
id="comment-${comment.comment_id}"
line="${comment.line_no}"
data-comment-id="${comment.comment_id}">
<div class="meta">
<div class="author">
${base.gravatar_with_user(comment.author.email, 16)}
@ -46,24 +53,15 @@
## only super-admin, repo admin OR comment owner can delete
%if not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed()):
%if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
<div onClick="deleteComment(${comment.comment_id})" class="delete-comment"> ${_('Delete')}</div>
%if inline:
<div class="comment-links-divider"> | </div>
## TODO: dan: add edit comment here
<a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
%if not comment.outdated:
<a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
<a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
%endif
%endif
%endif
%if inline:
<div id="prev_c_${comment.comment_id}" class="comment-previous-link" title="${_('Previous comment')}">
<a class="arrow_comment_link disabled"><i class="icon-left"></i></a>
</div>
<div id="next_c_${comment.comment_id}" class="comment-next-link" title="${_('Next comment')}">
<a class="arrow_comment_link disabled"><i class="icon-right"></i></a>
</div>
%endif
</div>
</div>
<div class="text">
@ -165,31 +163,8 @@
</%def>
## generates inlines taken from c.comments var
<%def name="inlines(is_pull_request=False)">
%if is_pull_request:
<h2 id="comments">${ungettext("%d Pull Request Comment", "%d Pull Request Comments", len(c.comments)) % len(c.comments)}</h2>
%else:
<h2 id="comments">${ungettext("%d Commit Comment", "%d Commit Comments", len(c.comments)) % len(c.comments)}</h2>
%endif
%for path, lines_comments in c.inline_comments:
% for line, comments in lines_comments.iteritems():
<div style="display: none;" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
## for each comment in particular line
%for comment in comments:
${comment_block(comment, inline=True)}
%endfor
</div>
%endfor
%endfor
</%def>
## generate inline comments and the main ones
## generate main comments
<%def name="generate_comments(include_pull_request=False, is_pull_request=False)">
## generate inlines for this changeset
${inlines(is_pull_request)}
%for comment in c.comments:
<div id="comment-tr-${comment.comment_id}">
## only render comments that are not from pull request, or from

View file

@ -54,12 +54,12 @@
##CS
<%include file="../compare/compare_commits.html"/>
<div class="cs_files">
${cbdiffs.render_diffset_menu()}
<%namespace name="cbdiffs" file="/codeblocks/diffs.html"/>
<%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
<%namespace name="diff_block" file="/changeset/diff_block.html"/>
${cbdiffs.render_diffset_menu()}
%for commit in c.commit_ranges:
${cbdifss.render_diffset(
${cbdiffs.render_diffset(
diffset=c.changes[commit.raw_id],
collapse_when_files_over=5,
commit=commit,

View file

@ -34,7 +34,74 @@ return h.url('', **new_args)
# add a ruler at to the output
ruler_at_chars=0,
# turn on inline comments
use_comments=False,
)">
%if use_comments:
<div id="cb-comments-inline-container-template" class="js-template">
${inline_comments_container([])}
</div>
<div class="js-template" id="cb-comment-inline-form-template">
<div class="comment-inline-form ac">
%if c.rhodecode_user.username != h.DEFAULT_USER:
${h.form('#', method='get')}
<div id="edit-container_{1}" class="clearfix">
<div class="comment-title pull-left">
${_('Create a comment on line {1}.')}
</div>
<div class="comment-help pull-right">
${(_('Comments parsed using %s syntax with %s support.') % (
('<a href="%s">%s</a>' % (h.url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
)
)|n
}
</div>
<div style="clear: both"></div>
<textarea id="text_{1}" name="text" class="comment-block-ta ac-input"></textarea>
</div>
<div id="preview-container_{1}" class="clearfix" style="display: none;">
<div class="comment-help">
${_('Comment preview')}
</div>
<div id="preview-box_{1}" class="preview-box"></div>
</div>
<div class="comment-footer">
<div class="action-buttons">
<input type="hidden" name="f_path" value="{0}">
<input type="hidden" name="line" value="{1}">
<button id="preview-btn_{1}" class="btn btn-secondary">${_('Preview')}</button>
<button id="edit-btn_{1}" class="btn btn-secondary" style="display: none;">${_('Edit')}</button>
${h.submit('save', _('Comment'), class_='btn btn-success save-inline-form')}
</div>
<div class="comment-button">
<button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
${_('Cancel')}
</button>
</div>
${h.end_form()}
</div>
%else:
${h.form('', class_='inline-form comment-form-login', method='get')}
<div class="pull-left">
<div class="comment-help pull-right">
${_('You need to be logged in to comment.')} <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a>
</div>
</div>
<div class="comment-button pull-right">
<button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
${_('Cancel')}
</button>
</div>
<div class="clearfix"></div>
${h.end_form()}
%endif
</div>
</div>
%endif
<%
collapse_all = len(diffset.files) > collapse_when_files_over
%>
@ -101,12 +168,12 @@ collapse_all = len(diffset.files) > collapse_when_files_over
<div
class="filediff"
data-f-path="${filediff['patch']['filename']}"
id="a_${h.FID(commit and commit.raw_id or '', filediff['patch']['filename'])}">
id="a_${h.FID('', filediff['patch']['filename'])}">
<label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
<div class="filediff-collapse-indicator"></div>
${diff_ops(filediff)}
</label>
${diff_menu(filediff)}
${diff_menu(filediff, use_comments=use_comments)}
<table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
%if not filediff.hunks:
%for op_id, op_text in filediff['patch']['stats']['ops'].items():
@ -159,9 +226,9 @@ collapse_all = len(diffset.files) > collapse_when_files_over
</td>
</tr>
%if c.diffmode == 'unified':
${render_hunk_lines_unified(hunk)}
${render_hunk_lines_unified(hunk, use_comments=use_comments)}
%elif c.diffmode == 'sideside':
${render_hunk_lines_sideside(hunk)}
${render_hunk_lines_sideside(hunk, use_comments=use_comments)}
%else:
<tr class="cb-line">
<td>unknown diff mode</td>
@ -227,7 +294,7 @@ from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
%endif
</span>
<a class="pill filediff-anchor" href="#a_${h.FID(commit and commit.raw_id or '', filediff.patch['filename'])}"></a>
<a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}"></a>
<span class="pill-group" style="float: right">
%if BIN_FILENODE in stats['ops']:
@ -250,7 +317,7 @@ from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
${filemode.startswith('100') and filemode[3:] or filemode}
</%def>
<%def name="diff_menu(filediff)">
<%def name="diff_menu(filediff, use_comments=False)">
<div class="filediff-menu">
%if filediff.diffset.source_ref:
%if filediff.patch['operation'] in ['D', 'M']:
@ -299,12 +366,41 @@ from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
>
${_('Download diff')}
</a>
## 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)
%if hasattr(c, 'ignorews_url'):
${c.ignorews_url(request.GET, h.FID('', filediff['patch']['filename']))}
%endif
%if hasattr(c, 'context_url'):
${c.context_url(request.GET, h.FID('', filediff['patch']['filename']))}
%endif
%if use_comments:
<a href="#" onclick="$(this).closest('.filediff').toggleClass('hide-comments'); return false;">
<span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
</a>
%endif
%endif
</div>
</%def>
<%def name="render_hunk_lines_sideside(hunk)">
<%namespace name="commentblock" file="/changeset/changeset_file_comment.html"/>
<%def name="inline_comments_container(comments)">
<div class="inline-comments">
%for comment in comments:
${commentblock.comment_block(comment, inline=True)}
%endfor
<span onclick="return Rhodecode.comments.createComment(this)"
class="btn btn-secondary cb-comment-add-button">
${_('Add another comment')}
</span>
</div>
</%def>
<%def name="render_hunk_lines_sideside(hunk, use_comments=False)">
%for i, line in enumerate(hunk.sideside):
<%
old_line_anchor, new_line_anchor = None, None
@ -326,7 +422,14 @@ from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
</td>
<td class="cb-content ${action_class(line.original.action)}"
data-line-number="o${line.original.lineno}"
><span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span>
>
%if use_comments and line.original.lineno:
${render_add_comment_button()}
%endif
<span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span>
%if use_comments and line.original.lineno and line.original.comments:
${inline_comments_container(line.original.comments)}
%endif
</td>
<td class="cb-lineno ${action_class(line.modified.action)}"
data-line-number="${line.modified.lineno}"
@ -341,15 +444,21 @@ from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
<td class="cb-content ${action_class(line.modified.action)}"
data-line-number="n${line.modified.lineno}"
>
%if use_comments and line.modified.lineno:
${render_add_comment_button()}
%endif
<span class="cb-code">${line.modified.action} ${line.modified.content or '' | n}</span>
%if use_comments and line.modified.lineno and line.modified.comments:
${inline_comments_container(line.modified.comments)}
%endif
</td>
</tr>
%endfor
</%def>
<%def name="render_hunk_lines_unified(hunk)">
%for old_line_no, new_line_no, action, content in hunk.unified:
<%def name="render_hunk_lines_unified(hunk, use_comments=False)">
%for old_line_no, new_line_no, action, content, comments in hunk.unified:
<%
old_line_anchor, new_line_anchor = None, None
if old_line_no:
@ -380,12 +489,25 @@ from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
</td>
<td class="cb-content ${action_class(action)}"
data-line-number="${new_line_no and 'n' or 'o'}${new_line_no or old_line_no}"
><span class="cb-code">${action} ${content or '' | n}</span>
</td>
>
%if use_comments:
${render_add_comment_button()}
%endif
<span class="cb-code">${action} ${content or '' | n}</span>
%if use_comments and comments:
${inline_comments_container(comments)}
%endif
</td>
</tr>
%endfor
</%def>
<%def name="render_add_comment_button()">
<button
class="btn btn-small btn-primary cb-comment-box-opener"
onclick="return Rhodecode.comments.createComment(this)"
>+</button>
</%def>
<%def name="render_diffset_menu()">
<div class="diffset-menu clearinner">

View file

@ -82,7 +82,7 @@ class TestChangesetController(object):
response.mustcontain('new file 100644')
response.mustcontain('Changed theme to ADC theme') # commit msg
self._check_diff_menus(response, right_menu=True)
self._check_new_diff_menus(response, right_menu=True)
def test_commit_range_page_different_ops(self, backend):
commit_id_range = {
@ -108,10 +108,13 @@ class TestChangesetController(object):
# svn is special
if backend.alias == 'svn':
response.mustcontain('new file 10644')
response.mustcontain('34 files changed: 1184 inserted, 311 deleted')
response.mustcontain('1 file changed: 5 inserted, 1 deleted')
response.mustcontain('12 files changed: 236 inserted, 22 deleted')
response.mustcontain('21 files changed: 943 inserted, 288 deleted')
else:
response.mustcontain('new file 100644')
response.mustcontain('33 files changed: 1165 inserted, 308 deleted')
response.mustcontain('12 files changed: 222 inserted, 20 deleted')
response.mustcontain('21 files changed: 943 inserted, 288 deleted')
# files op files
response.mustcontain('File no longer present at commit: %s' %
@ -119,7 +122,7 @@ class TestChangesetController(object):
response.mustcontain('Added docstrings to vcs.cli') # commit msg
response.mustcontain('Changed theme to ADC theme') # commit msg
self._check_diff_menus(response)
self._check_new_diff_menus(response)
def test_combined_compare_commit_page_different_ops(self, backend):
commit_id_range = {

View file

@ -109,11 +109,17 @@ class TestCommitCommentsController(TestController):
# test DB
assert ChangesetComment.query().count() == 1
assert_comment_links(response, 0, ChangesetComment.query().count())
response.mustcontain(
'''class="inline-comment-placeholder" '''
'''path="vcs/web/simplevcs/views/repository.py" '''
'''target_id="vcswebsimplevcsviewsrepositorypy"'''
)
if backend.alias == 'svn':
response.mustcontain(
'''data-f-path="vcs/commands/summary.py" '''
'''id="a_c--ad05457a43f8"'''
)
else:
response.mustcontain(
'''data-f-path="vcs/backends/hg.py" '''
'''id="a_c--9c390eb52cd6"'''
)
assert Notification.query().count() == 1
assert ChangesetComment.query().count() == 1
@ -271,7 +277,6 @@ def assert_comment_links(response, comments, inline_comments):
inline_comments) % inline_comments
if inline_comments:
response.mustcontain(
'<a href="#inline-comments" '
'id="inline-comments-counter">%s</a>' % inline_comments_text)
'id="inline-comments-counter">%s</' % inline_comments_text)
else:
response.mustcontain(inline_comments_text)