@@ -359,16 +359,25 @@ class ChangesetController(BaseRepoContro
return render('changeset/raw_changeset.html')
@jsonify
def comment(self, repo_name, revision):
ChangesetCommentsModel().create(text=request.POST.get('text'),
repo_id=c.rhodecode_db_repo.repo_id,
user_id=c.rhodecode_user.user_id,
revision=revision,
f_path=request.POST.get('f_path'),
line_no=request.POST.get('line'))
comm = ChangesetCommentsModel().create(
text=request.POST.get('text'),
line_no=request.POST.get('line')
)
Session.commit()
return redirect(h.url('changeset_home', repo_name=repo_name,
revision=revision))
data = {
'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
}
if comm:
c.co = comm
data.update(comm.get_dict())
data.update({'rendered_text': render('changeset/changeset_comment_block.html')})
return data
def delete_comment(self, repo_name, comment_id):
@@ -142,7 +142,9 @@ class ChangesetCommentsModel(BaseModel):
.filter(ChangesetComment.repo_id == repo_id)\
.filter(ChangesetComment.revision == revision)\
.filter(ChangesetComment.line_no != None)\
.filter(ChangesetComment.f_path != None).all()
.filter(ChangesetComment.f_path != None)\
.order_by(ChangesetComment.comment_id.asc())\
.all()
paths = defaultdict(lambda: defaultdict(list))
@@ -3966,6 +3966,7 @@ form.comment-form {
.comment .buttons {
float: right;
padding:2px 2px 0px 0px;
@@ -3975,6 +3976,23 @@ form.comment-form {
/** comment inline form **/
.comment-inline-form .overlay{
display: none;
.comment-inline-form .overlay.submitting{
display:block;
background: none repeat scroll 0 0 white;
font-size: 16px;
opacity: 0.5;
position: absolute;
text-align: center;
vertical-align: top;
.comment-inline-form .overlay.submitting .overlay-text{
width:100%;
margin-top:5%;
.comment-inline-form .clearfix{
background: #EEE;
@@ -3987,6 +4005,7 @@ form.comment-form {
div.comment-inline-form {
margin-top: 5px;
padding:2px 6px 8px 6px;
.comment-inline-form strong {
@@ -4047,6 +4066,10 @@ form.comment-inline-form {
margin: 3px 3px 5px 5px;
background-color: #FAFAFA;
.inline-comments .add-comment {
padding: 2px 4px 8px 5px;
.inline-comments .comment-wrapp{
padding:1px;
@@ -4078,7 +4101,7 @@ form.comment-inline-form {
.inline-comments-button .add-comment{
margin:10px 5px !important;
margin:2px 0px 8px 5px !important
.notifications{
border-radius: 4px 4px 4px 4px;
@@ -195,6 +195,31 @@ function ypjax(url,container,s_call,f_ca
};
var ajaxPOST = function(url,postData,success) {
var toQueryString = function(o) {
if(typeof o !== 'object') {
return false;
var _p, _qs = [];
for(_p in o) {
_qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
return _qs.join('&');
var sUrl = url;
var callback = {
success: success,
failure: function (o) {
alert("error");
},
var postData = toQueryString(postData);
var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
return request;
/**
* tooltip activate
*/
@@ -300,33 +325,25 @@ var q_filter = function(target,nodes,dis
var postData = postData;
var tableTr = function(cls,body){
var tr = document.createElement('tr');
YUD.addClass(tr, cls);
var cont = new YAHOO.util.Element(body);
var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
tr.id = 'comment-tr-{0}'.format(comment_id);
tr.innerHTML = '<td class="lineno-inline new-inline"></td>'+
'<td class="lineno-inline old-inline"></td>'+
'<td>{0}</td>'.format(body);
return tr;
/** comments **/
var removeInlineForm = function(form) {
form.parentNode.removeChild(form);
var form = document.createElement('tr');
YUD.addClass(form, cls);
form.innerHTML = '<td class="lineno-inline new-inline"></td>'+
return form;
var createInlineForm = function(parent_tr, f_path, line) {
var tmpl = YUD.get('comment-inline-form-template').innerHTML;
tmpl = tmpl.format(f_path, line);
@@ -337,12 +354,27 @@ var createInlineForm = function(parent_t
var form_hide_button = new YAHOO.util.Element(form.getElementsByClassName('hide-inline-form')[0]);
form_hide_button.on('click', function(e) {
var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
YUD.setStyle(newtr.nextElementSibling,'display','');
removeInlineForm(newtr);
YUD.removeClass(parent_tr, 'form-open');
});
return form
* Inject inline comment for on given TR this tr should be always an .line
* tr containing the line. Code will detect comment, and always put the comment
* block at the very bottom
var injectInlineForm = function(tr){
if(!YUD.hasClass(tr, 'line')){
return
var submit_url = AJAX_COMMENT_URL;
if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(tr,'no-comment')){
@@ -350,20 +382,92 @@ var injectInlineForm = function(tr){
var node = tr.parentNode.parentNode.parentNode.getElementsByClassName('full_f_path')[0];
var f_path = YUD.getAttribute(node,'path');
var lineno = getLineNo(tr);
var form = createInlineForm(tr, f_path, lineno);
var target_tr = tr;
if(YUD.hasClass(YUD.getNextSibling(tr),'inline-comments')){
target_tr = YUD.getNextSibling(tr);
YUD.insertAfter(form,target_tr);
var form = createInlineForm(tr, f_path, lineno, submit_url);
var parent = tr;
while (1){
var n = parent.nextElementSibling;
// next element are comments !
if(YUD.hasClass(n,'inline-comments')){
parent = n;
else{
break;
YUD.insertAfter(form,parent);
YUD.get('text_'+lineno).focus();
var f = YUD.get(form);
var overlay = f.getElementsByClassName('overlay')[0];
var _form = f.getElementsByClassName('inline-form')[0];
form.on('submit',function(e){
YUE.preventDefault(e);
//ajax submit
var text = YUD.get('text_'+lineno).value;
var postData = {
'text':text,
'f_path':f_path,
'line':lineno
if(lineno === undefined){
alert('missing line !');
if(f_path === undefined){
alert('missing file path !');
var success = function(o){
YUD.removeClass(tr, 'form-open');
removeInlineForm(f);
var json_data = JSON.parse(o.responseText);
renderInlineComment(json_data);
if (YUD.hasClass(overlay,'overlay')){
var w = _form.offsetWidth;
var h = _form.offsetHeight;
YUD.setStyle(overlay,'width',w+'px');
YUD.setStyle(overlay,'height',h+'px');
YUD.addClass(overlay, 'submitting');
ajaxPOST(submit_url, postData, success);
tooltip_activate();
var createInlineAddButton = function(tr,label){
var html = '<div class="add-comment"><span class="ui-btn">{0}</span></div>'.format(label);
var add = new YAHOO.util.Element(tableTr('inline-comments-button',html));
var deleteComment = function(comment_id){
var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
var postData = {'_method':'delete'};
var n = YUD.get('comment-tr-'+comment_id);
var root = n.previousElementSibling.previousElementSibling;
n.parentNode.removeChild(n);
// scann nodes, and attach add button to last one
placeAddButton(root);
ajaxPOST(url,postData,success);
var createInlineAddButton = function(tr){
var label = TRANSLATION_MAP['add another comment'];
var html_el = document.createElement('div');
YUD.addClass(html_el, 'add-comment');
html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
var add = new YAHOO.util.Element(html_el);
add.on('click', function(e) {
injectInlineForm(tr);
@@ -384,6 +488,103 @@ var getLineNo = function(tr) {
return line
var placeAddButton = function(target_tr){
if(!target_tr){
var last_node = target_tr;
//scann
var n = last_node.nextElementSibling;
last_node = n;
//also remove the comment button from previos
var comment_add_buttons = last_node.getElementsByClassName('add-comment');
for(var i=0;i<comment_add_buttons.length;i++){
var b = comment_add_buttons[i];
b.parentNode.removeChild(b);
var add = createInlineAddButton(target_tr);
// get the comment div
var comment_block = last_node.getElementsByClassName('comment')[0];
// attach add button
YUD.insertAfter(add,comment_block);
* Places the inline comment into the changeset block in proper line position
var placeInline = function(target_container,lineno,html){
var lineid = "{0}_{1}".format(target_container,lineno);
var target_line = YUD.get(lineid);
var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
// check if there are comments already !
var parent = target_line.parentNode;
var root_parent = parent;
// put in the comment at the bottom
YUD.insertAfter(comment,parent);
placeAddButton(root_parent);
return target_line;
* make a single inline comment and place it inside
var renderInlineComment = function(json_data){
try{
var html = json_data['rendered_text'];
var lineno = json_data['line_no'];
var target_id = json_data['target_id'];
placeInline(target_id, lineno, html);
}catch(e){
console.log(e);
* Iterates over all the inlines, and places them inside proper blocks of data
var renderInlineComments = function(file_comments){
for (f in file_comments){
// holding all comments for a FILE
var box = file_comments[f];
var target_id = YUD.getAttribute(box,'target_id');
// actually comments with line numbers
var comments = box.children;
for(var i=0; i<comments.length; i++){
var data = {
'rendered_text': comments[i].outerHTML,
'line_no': YUD.getAttribute(comments[i],'line'),
'target_id': target_id
renderInlineComment(data);
var fileBrowserListeners = function(current_url, node_list_url, url_base,
truncated_lbl, nomatch_lbl){
@@ -47,9 +47,13 @@
<script type="text/javascript">
var follow_base_url = "${h.url('toggle_following')}";
var stop_follow_text = "${_('Stop following this repository')}";
var start_follow_text = "${_('Start following this repository')}";
//JS translations map
var TRANSLATION_MAP = {
'add another comment':'${_("add another comment")}',
'Stop following this repository':"${_('Stop following this repository')}",
'Start following this repository':"${_('Start following this repository')}",
var onSuccessFollow = function(target){
var f = YUD.get(target.id);
@@ -57,7 +61,7 @@
if(f.getAttribute('class')=='follow'){
f.setAttribute('class','following');
f.setAttribute('title',stop_follow_text);
f.setAttribute('title',TRANSLATION_MAP['Stop following this repository']);
if(f_cnt){
var cnt = Number(f_cnt.innerHTML)+1;
@@ -66,7 +70,7 @@
f.setAttribute('class','follow');
f.setAttribute('title',start_follow_text);
f.setAttribute('title',TRANSLATION_MAP['Start following this repository']);
f_cnt.innerHTML = cnt;
@@ -122,22 +122,12 @@
<%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
${comment.comment_inline_form(c.changeset)}
## render comments
${comment.comments(c.changeset)}
var url = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}".replace('__COMMENT_ID__',comment_id);
var postData = '_method=delete';
var n = YUD.get('comment-'+comment_id);
YUE.onDOMReady(function(){
AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}"
YUE.on(YUQ('.show-inline-comments'),'change',function(e){
var show = 'none';
var target = e.currentTarget;
@@ -162,28 +152,7 @@
// inject comments into they proper positions
var file_comments = YUQ('.inline-comment-placeholder');
var inlines = box.children;
for(var i=0; i<inlines.length; i++){
var inline = inlines[i];
var lineno = YUD.getAttribute(inlines[i],'line');
var lineid = "{0}_{1}".format(YUD.getAttribute(inline,'target_id'),lineno);
var add = createInlineAddButton(target_line.parentNode,'${_("add another comment")}');
YUD.insertAfter(add,target_line.parentNode);
var comment = new YAHOO.util.Element(tableTr('inline-comments',inline.innerHTML))
YUD.insertAfter(comment,target_line.parentNode);
renderInlineComments(file_comments);
})
</script>
new file 100644
${comment.comment_block(c.co)}
\ No newline at end of file
@@ -4,7 +4,7 @@
## ${comment.comment_block(co)}
##
<%def name="comment_block(co)">
<div class="comment" id="comment-${co.comment_id}">
<div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
<div class="comment-wrapp">
<div class="meta">
<span class="user">
@@ -32,7 +32,8 @@
<div id='comment-inline-form-template' style="display:none">
<div class="comment-inline-form">
%if c.rhodecode_user.username != 'default':
${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id))}
<div class="overlay"><div class="overlay-text">${_('Submitting...')}</div></div>
${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id),class_='inline-form')}
<div class="clearfix">
<div class="comment-help">${_('Commenting on line')} {1}. ${_('Comments parsed using')}
<a href="${h.url('rst_help')}">RST</a> ${_('syntax')} ${_('with')}
@@ -43,7 +44,7 @@
<div class="comment-button">
<input type="hidden" name="f_path" value="{0}">
<input type="hidden" name="line" value="{1}">
${h.submit('save', _('Comment'), class_='ui-btn')}
${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
</div>
${h.end_form()}
@@ -64,23 +65,27 @@
</%def>
<%def name="comments(changeset)">
<div class="comments">
<%def name="inlines(changeset)">
<div class="comments-number">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
%for path, lines in c.inline_comments:
<div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.FID(changeset.raw_id,path)}">
% for line,comments in lines.iteritems():
<div class="inline-comment-placeholder-line" line="${line}" target_id="${h.safeid(h.safe_unicode(path))}">
<div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
%for co in comments:
${comment_block(co)}
%endfor
<div id="inline-comments-container">
${inlines(changeset)}
%for co in c.comments:
Status change: