Changeset - 3052f90c568a
[Not reviewed]
beta
0 3 0
Marcin Kuzminski - 14 years ago 2012-04-14 22:40:47
marcin@python-works.com
fixed main comments, prevent from sending inline comments if text is empty
3 files changed with 16 insertions and 2 deletions:
0 comments (0 inline, 0 general)
rhodecode/controllers/changeset.py
Show inline comments
 
@@ -277,116 +277,121 @@ class ChangesetController(BaseRepoContro
 
                )
 
            #==================================================================
 
            # REMOVED FILES
 
            #==================================================================
 
            for node in changeset.removed:
 
                c.changes[changeset.raw_id].append(
 
                    ('removed', node, None, None, None, (0, 0))
 
                )
 

	
 
        # count inline comments
 
        for path, lines in c.inline_comments:
 
            for comments in lines.values():
 
                c.inline_cnt += len(comments)
 

	
 
        if len(c.cs_ranges) == 1:
 
            c.changeset = c.cs_ranges[0]
 
            c.changes = c.changes[c.changeset.raw_id]
 

	
 
            return render('changeset/changeset.html')
 
        else:
 
            return render('changeset/changeset_range.html')
 

	
 
    def raw_changeset(self, revision):
 

	
 
        method = request.GET.get('diff', 'show')
 
        ignore_whitespace = request.GET.get('ignorews') == '1'
 
        line_context = request.GET.get('context', 3)
 
        try:
 
            c.scm_type = c.rhodecode_repo.alias
 
            c.changeset = c.rhodecode_repo.get_changeset(revision)
 
        except RepositoryError:
 
            log.error(traceback.format_exc())
 
            return redirect(url('home'))
 
        else:
 
            try:
 
                c.changeset_parent = c.changeset.parents[0]
 
            except IndexError:
 
                c.changeset_parent = None
 
            c.changes = []
 

	
 
            for node in c.changeset.added:
 
                filenode_old = FileNode(node.path, '')
 
                if filenode_old.is_binary or node.is_binary:
 
                    diff = _('binary file') + '\n'
 
                else:
 
                    f_gitdiff = diffs.get_gitdiff(filenode_old, node,
 
                                           ignore_whitespace=ignore_whitespace,
 
                                           context=line_context)
 
                    diff = diffs.DiffProcessor(f_gitdiff,
 
                                                format='gitdiff').raw_diff()
 

	
 
                cs1 = None
 
                cs2 = node.changeset.raw_id
 
                c.changes.append(('added', node, diff, cs1, cs2))
 

	
 
            for node in c.changeset.changed:
 
                filenode_old = c.changeset_parent.get_node(node.path)
 
                if filenode_old.is_binary or node.is_binary:
 
                    diff = _('binary file')
 
                else:
 
                    f_gitdiff = diffs.get_gitdiff(filenode_old, node,
 
                                           ignore_whitespace=ignore_whitespace,
 
                                           context=line_context)
 
                    diff = diffs.DiffProcessor(f_gitdiff,
 
                                                format='gitdiff').raw_diff()
 

	
 
                cs1 = filenode_old.changeset.raw_id
 
                cs2 = node.changeset.raw_id
 
                c.changes.append(('changed', node, diff, cs1, cs2))
 

	
 
        response.content_type = 'text/plain'
 

	
 
        if method == 'download':
 
            response.content_disposition = 'attachment; filename=%s.patch' \
 
                                            % revision
 

	
 
        c.parent_tmpl = ''.join(['# Parent  %s\n' % x.raw_id
 
                                 for x in c.changeset.parents])
 

	
 
        c.diffs = ''
 
        for x in c.changes:
 
            c.diffs += x[2]
 

	
 
        return render('changeset/raw_changeset.html')
 

	
 
    @jsonify
 
    def comment(self, repo_name, revision):
 
        comm = 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')
 
        )
 
        Session.commit()
 
        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            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
 

	
 
    @jsonify
 
    def delete_comment(self, repo_name, comment_id):
 
        co = ChangesetComment.get(comment_id)
 
        owner = lambda: co.author.user_id == c.rhodecode_user.user_id
 
        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
 
            ChangesetCommentsModel().delete(comment=co)
 
            Session.commit()
 
            return True
 
        else:
 
            raise HTTPForbidden()
rhodecode/public/js/rhodecode.js
Show inline comments
 
@@ -103,192 +103,195 @@ ColorGenerator.prototype = {
 
        RGB_tuple = this._hsvToRgb(HSV_tuple[0],HSV_tuple[1],HSV_tuple[2]);
 
        function toRgb(v){
 
        	return ""+parseInt(v*256)
 
        }
 
        return [toRgb(RGB_tuple[0]),toRgb(RGB_tuple[1]),toRgb(RGB_tuple[2])];
 
        
 
    }
 
}
 

	
 

	
 

	
 

	
 

	
 
/**
 
 * GLOBAL YUI Shortcuts
 
 */
 
var YUC = YAHOO.util.Connect;
 
var YUD = YAHOO.util.Dom;
 
var YUE = YAHOO.util.Event;
 
var YUQ = YAHOO.util.Selector.query;
 

	
 
// defines if push state is enabled for this browser ?
 
var push_state_enabled = Boolean(
 
		window.history && window.history.pushState && window.history.replaceState
 
		&& !(   /* disable for versions of iOS before version 4.3 (8F190) */
 
				(/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent)
 
				/* disable for the mercury iOS browser, or at least older versions of the webkit engine */
 
				|| (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
 
		)
 
);
 

	
 
var _run_callbacks = function(callbacks){
 
	if (callbacks !== undefined){
 
		var _l = callbacks.length;
 
	    for (var i=0;i<_l;i++){
 
	    	var func = callbacks[i];
 
	    	if(typeof(func)=='function'){
 
	            try{
 
	          	    func();
 
	            }catch (err){};            		
 
	    	}
 
	    }
 
	}		
 
}
 

	
 
/**
 
 * Partial Ajax Implementation
 
 * 
 
 * @param url: defines url to make partial request
 
 * @param container: defines id of container to input partial result
 
 * @param s_call: success callback function that takes o as arg
 
 *  o.tId
 
 *  o.status
 
 *  o.statusText
 
 *  o.getResponseHeader[ ]
 
 *  o.getAllResponseHeaders
 
 *  o.responseText
 
 *  o.responseXML
 
 *  o.argument
 
 * @param f_call: failure callback
 
 * @param args arguments 
 
 */
 
function ypjax(url,container,s_call,f_call,args){
 
	var method='GET';
 
	if(args===undefined){
 
		args=null;
 
	}
 
	
 
	// Set special header for partial ajax == HTTP_X_PARTIAL_XHR
 
	YUC.initHeader('X-PARTIAL-XHR',true);
 
	
 
	// wrapper of passed callback
 
	var s_wrapper = (function(o){
 
		return function(o){
 
			YUD.get(container).innerHTML=o.responseText;
 
			YUD.setStyle(container,'opacity','1.0');
 
    		//execute the given original callback
 
    		if (s_call !== undefined){
 
    			s_call(o);
 
    		}
 
		}
 
	})()	
 
	YUD.setStyle(container,'opacity','0.3');
 
	YUC.asyncRequest(method,url,{
 
		success:s_wrapper,
 
		failure:function(o){
 
			console.log(o);
 
			YUD.get(container).innerHTML='ERROR';
 
			YUD.setStyle(container,'opacity','1.0');
 
			YUD.setStyle(container,'color','red');
 
		}
 
	},args);
 
	
 
};
 

	
 
var ajaxPOST = function(url,postData,success) {
 
	// Set special header for ajax == HTTP_X_PARTIAL_XHR
 
	YUC.initHeader('X-PARTIAL-XHR',true);
 
	
 
	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
 
 */
 
var tooltip_activate = function(){
 
    function toolTipsId(){
 
        var ids = [];
 
        var tts = YUQ('.tooltip');
 
        for (var i = 0; i < tts.length; i++) {
 
            // if element doesn't not have and id 
 
        	//  autogenerate one for tooltip 
 
            if (!tts[i].id){
 
                tts[i].id='tt'+((i*100)+tts.length);
 
            }
 
            ids.push(tts[i].id);
 
        }
 
        return ids
 
    };
 
    var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
 
        context: [[toolTipsId()],"tl","bl",null,[0,5]],
 
        monitorresize:false,
 
        xyoffset :[0,0],
 
        autodismissdelay:300000,
 
        hidedelay:5,
 
        showdelay:20,
 
    });
 
};
 

	
 
/**
 
 * show more
 
 */
 
var show_more_event = function(){
 
    YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
 
        var el = e.target;
 
        YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
 
        YUD.setStyle(el.parentNode,'display','none');
 
    });
 
};
 

	
 

	
 
/**
 
 * Quick filter widget
 
 * 
 
 * @param target: filter input target
 
 * @param nodes: list of nodes in html we want to filter.
 
 * @param display_element function that takes current node from nodes and
 
 *    does hide or show based on the node
 
 * 
 
 */
 
var q_filter = function(target,nodes,display_element){
 
	
 
	var nodes = nodes;
 
	var q_filter_field = YUD.get(target);
 
	var F = YAHOO.namespace(target);
 

	
 
	YUE.on(q_filter_field,'click',function(){
 
	   q_filter_field.value = '';
 
	});
 

	
 
	YUE.on(q_filter_field,'keyup',function(e){
 
	    clearTimeout(F.filterTimeout); 
 
	    F.filterTimeout = setTimeout(F.updateFilter,600); 
 
	});
 

	
 
	F.filterTimeout = null;
 

	
 
	var show_node = function(node){
 
		YUD.setStyle(node,'display','')
 
	}
 
	var hide_node = function(node){
 
		YUD.setStyle(node,'display','none');
 
	}
 
	
 
@@ -330,223 +333,227 @@ var tableTr = function(cls,body){
 
	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 createInlineForm = function(parent_tr, f_path, line) {
 
	var tmpl = YUD.get('comment-inline-form-template').innerHTML;
 
	tmpl = tmpl.format(f_path, line);
 
	var form = tableTr('comment-form-inline',tmpl)
 
	
 
	// create event for hide button
 
	form = new YAHOO.util.Element(form);
 
	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')){
 
		  return
 
	  }	
 
	  YUD.addClass(tr,'form-open');
 
	  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, 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 !');
 
			  return
 
		  }
 
		  if(f_path === undefined){
 
			  alert('missing file path !');
 
			  return
 
		  }
 
		  
 
		  if(text == ""){
 
			  return
 
		  }
 
		  
 
		  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 deleteComment = function(comment_id){
 
	var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
 
    var postData = {'_method':'delete'};
 
    var success = function(o){
 
        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);        
 
        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);
 
	});
 
	return add;
 
};
 

	
 
var getLineNo = function(tr) {
 
	var line;
 
	var o = tr.children[0].id.split('_');
 
	var n = tr.children[1].id.split('_');
 

	
 
	if (n.length >= 2) {
 
		line = n[n.length-1];
 
	} else if (o.length >= 2) {
 
		line = o[o.length-1];
 
	}
 

	
 
	return line
 
};
 

	
 
var placeAddButton = function(target_tr){
 
	if(!target_tr){
 
		return
 
	}
 
	var last_node = target_tr;
 
    //scann	
 
	  while (1){
 
		  var n = last_node.nextElementSibling;
 
		  // next element are comments !
 
		  if(YUD.hasClass(n,'inline-comments')){
 
			  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);
 
			  }
 
		  }
 
		  else{
 
			  break;
 
		  }
 
	  }
 
	  
 
    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;
 
	  while (1){
 
		  var n = parent.nextElementSibling;
 
		  // next element are comments !
 
		  if(YUD.hasClass(n,'inline-comments')){
 
			  parent = n;
 
		  }
 
		  else{
 
			  break;
 
		  }
 
	  }
 
	  // put in the comment at the bottom
 
	  YUD.insertAfter(comment,parent);
 
	  
 
	  // scann nodes, and attach add button to last one
 
      placeAddButton(root_parent);
 

	
 
	  return target_line;
 
}
 

	
 
/**
 
 * make a single inline comment and place it inside
rhodecode/templates/changeset/changeset_file_comment.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
## usage:
 
## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
## ${comment.comment_block(co)}
 
##
 
<%def name="comment_block(co)">
 
  <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
 
    <div class="comment-wrapp">
 
  	<div class="meta">
 
  		<span class="user">
 
  			<img src="${h.gravatar_url(co.author.email, 20)}" />
 
  			${co.author.username}
 
  		</span>
 
  		<span class="date">
 
  			${h.age(co.modified_at)}
 
  		</span>
 
      %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
 
        <span class="buttons">
 
          <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
 
        </span>
 
      %endif
 
  	</div>
 
  	<div class="text">
 
  		${h.rst_w_mentions(co.text)|n}
 
  	</div>
 
    </div>
 
  </div>
 
</%def>
 

	
 

	
 
<%def name="comment_inline_form(changeset)">
 
<div id='comment-inline-form-template' style="display:none">
 
  <div class="comment-inline-form">
 
  %if c.rhodecode_user.username != 'default':
 
    <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')}
 
          <span style="color:#003367" class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> ${_('support')}
 
          </div>
 
          <textarea id="text_{1}" name="text"></textarea>
 
      </div>
 
      <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 save-inline-form')}
 
      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
 
      </div>
 
      ${h.end_form()}
 
  %else:
 
      ${h.form('')}
 
      <div class="clearfix">
 
          <div class="comment-help">
 
            ${'You need to be logged in to comment.'} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
 
          </div>
 
      </div>
 
      <div class="comment-button">
 
      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
 
      </div>
 
      ${h.end_form()}
 
  %endif
 
  </div>
 
</div>
 
</%def>
 

	
 

	
 
<%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:
 
        % for line,comments in lines.iteritems():
 
            <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>
 
        %endfor
 
    %endfor
 

	
 
</%def>
 

	
 
<%def name="comments(changeset)">
 

	
 
<div class="comments">
 
    <div id="inline-comments-container">
 
     ${inlines(changeset)}
 
    </div>
 

	
 
    %for co in c.comments:
 
        ${comment_block(co)}
 
        <div id="comment-tr-${co.comment_id}">
 
          ${comment_block(co)}
 
        </div>
 
    %endfor
 
    %if c.rhodecode_user.username != 'default':
 
    <div class="comment-form">
 
        ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id))}
 
        <strong>${_('Leave a comment')}</strong>
 
        <div class="clearfix">
 
            <div class="comment-help">
 
                ${_('Comments parsed using')} <a href="${h.url('rst_help')}">RST</a> ${_('syntax')}
 
                ${_('with')} <span style="color:#003367" class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> ${_('support')}
 
            </div>
 
                ${h.textarea('text')}
 
        </div>
 
        <div class="comment-button">
 
        ${h.submit('save', _('Comment'), class_='ui-button')}
 
        </div>
 
        ${h.end_form()}
 
    </div>
 
    %endif
 
</div>
 
</%def>
0 comments (0 inline, 0 general)