##// END OF EJS Templates
#415: Adding comment to changeset causes reload...
marcink -
r2187:b61e5401 beta
parent child Browse files
Show More
@@ -0,0 +1,2 b''
1 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
2 ${comment.comment_block(c.co)} No newline at end of file
@@ -359,16 +359,25 b' class ChangesetController(BaseRepoContro'
359
359
360 return render('changeset/raw_changeset.html')
360 return render('changeset/raw_changeset.html')
361
361
362 @jsonify
362 def comment(self, repo_name, revision):
363 def comment(self, repo_name, revision):
363 ChangesetCommentsModel().create(text=request.POST.get('text'),
364 comm = ChangesetCommentsModel().create(
364 repo_id=c.rhodecode_db_repo.repo_id,
365 text=request.POST.get('text'),
365 user_id=c.rhodecode_user.user_id,
366 repo_id=c.rhodecode_db_repo.repo_id,
366 revision=revision,
367 user_id=c.rhodecode_user.user_id,
367 f_path=request.POST.get('f_path'),
368 revision=revision,
368 line_no=request.POST.get('line'))
369 f_path=request.POST.get('f_path'),
370 line_no=request.POST.get('line')
371 )
369 Session.commit()
372 Session.commit()
370 return redirect(h.url('changeset_home', repo_name=repo_name,
373 data = {
371 revision=revision))
374 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
375 }
376 if comm:
377 c.co = comm
378 data.update(comm.get_dict())
379 data.update({'rendered_text': render('changeset/changeset_comment_block.html')})
380 return data
372
381
373 @jsonify
382 @jsonify
374 def delete_comment(self, repo_name, comment_id):
383 def delete_comment(self, repo_name, comment_id):
@@ -142,7 +142,9 b' class ChangesetCommentsModel(BaseModel):'
142 .filter(ChangesetComment.repo_id == repo_id)\
142 .filter(ChangesetComment.repo_id == repo_id)\
143 .filter(ChangesetComment.revision == revision)\
143 .filter(ChangesetComment.revision == revision)\
144 .filter(ChangesetComment.line_no != None)\
144 .filter(ChangesetComment.line_no != None)\
145 .filter(ChangesetComment.f_path != None).all()
145 .filter(ChangesetComment.f_path != None)\
146 .order_by(ChangesetComment.comment_id.asc())\
147 .all()
146
148
147 paths = defaultdict(lambda: defaultdict(list))
149 paths = defaultdict(lambda: defaultdict(list))
148
150
@@ -3966,6 +3966,7 b' form.comment-form {'
3966
3966
3967 .comment .buttons {
3967 .comment .buttons {
3968 float: right;
3968 float: right;
3969 padding:2px 2px 0px 0px;
3969 }
3970 }
3970
3971
3971
3972
@@ -3975,6 +3976,23 b' form.comment-form {'
3975 }
3976 }
3976
3977
3977 /** comment inline form **/
3978 /** comment inline form **/
3979 .comment-inline-form .overlay{
3980 display: none;
3981 }
3982 .comment-inline-form .overlay.submitting{
3983 display:block;
3984 background: none repeat scroll 0 0 white;
3985 font-size: 16px;
3986 opacity: 0.5;
3987 position: absolute;
3988 text-align: center;
3989 vertical-align: top;
3990
3991 }
3992 .comment-inline-form .overlay.submitting .overlay-text{
3993 width:100%;
3994 margin-top:5%;
3995 }
3978
3996
3979 .comment-inline-form .clearfix{
3997 .comment-inline-form .clearfix{
3980 background: #EEE;
3998 background: #EEE;
@@ -3987,6 +4005,7 b' form.comment-form {'
3987 div.comment-inline-form {
4005 div.comment-inline-form {
3988 margin-top: 5px;
4006 margin-top: 5px;
3989 padding:2px 6px 8px 6px;
4007 padding:2px 6px 8px 6px;
4008
3990 }
4009 }
3991
4010
3992 .comment-inline-form strong {
4011 .comment-inline-form strong {
@@ -4047,6 +4066,10 b' form.comment-inline-form {'
4047 margin: 3px 3px 5px 5px;
4066 margin: 3px 3px 5px 5px;
4048 background-color: #FAFAFA;
4067 background-color: #FAFAFA;
4049 }
4068 }
4069 .inline-comments .add-comment {
4070 padding: 2px 4px 8px 5px;
4071 }
4072
4050 .inline-comments .comment-wrapp{
4073 .inline-comments .comment-wrapp{
4051 padding:1px;
4074 padding:1px;
4052 }
4075 }
@@ -4078,7 +4101,7 b' form.comment-inline-form {'
4078 font-size: 16px;
4101 font-size: 16px;
4079 }
4102 }
4080 .inline-comments-button .add-comment{
4103 .inline-comments-button .add-comment{
4081 margin:10px 5px !important;
4104 margin:2px 0px 8px 5px !important
4082 }
4105 }
4083 .notifications{
4106 .notifications{
4084 border-radius: 4px 4px 4px 4px;
4107 border-radius: 4px 4px 4px 4px;
@@ -195,6 +195,31 b' function ypjax(url,container,s_call,f_ca'
195
195
196 };
196 };
197
197
198 var ajaxPOST = function(url,postData,success) {
199 var toQueryString = function(o) {
200 if(typeof o !== 'object') {
201 return false;
202 }
203 var _p, _qs = [];
204 for(_p in o) {
205 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
206 }
207 return _qs.join('&');
208 };
209
210 var sUrl = url;
211 var callback = {
212 success: success,
213 failure: function (o) {
214 alert("error");
215 },
216 };
217 var postData = toQueryString(postData);
218 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
219 return request;
220 };
221
222
198 /**
223 /**
199 * tooltip activate
224 * tooltip activate
200 */
225 */
@@ -300,33 +325,25 b' var q_filter = function(target,nodes,dis'
300 }
325 }
301 };
326 };
302
327
303 var ajaxPOST = function(url,postData,success) {
328 var tableTr = function(cls,body){
304 var sUrl = url;
329 var tr = document.createElement('tr');
305 var callback = {
330 YUD.addClass(tr, cls);
306 success: success,
331
307 failure: function (o) {
332
308 alert("error");
333 var cont = new YAHOO.util.Element(body);
309 },
334 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
310 };
335 tr.id = 'comment-tr-{0}'.format(comment_id);
311 var postData = postData;
336 tr.innerHTML = '<td class="lineno-inline new-inline"></td>'+
312 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
337 '<td class="lineno-inline old-inline"></td>'+
338 '<td>{0}</td>'.format(body);
339 return tr;
313 };
340 };
314
341
315
316 /** comments **/
342 /** comments **/
317 var removeInlineForm = function(form) {
343 var removeInlineForm = function(form) {
318 form.parentNode.removeChild(form);
344 form.parentNode.removeChild(form);
319 };
345 };
320
346
321 var tableTr = function(cls,body){
322 var form = document.createElement('tr');
323 YUD.addClass(form, cls);
324 form.innerHTML = '<td class="lineno-inline new-inline"></td>'+
325 '<td class="lineno-inline old-inline"></td>'+
326 '<td>{0}</td>'.format(body);
327 return form;
328 };
329
330 var createInlineForm = function(parent_tr, f_path, line) {
347 var createInlineForm = function(parent_tr, f_path, line) {
331 var tmpl = YUD.get('comment-inline-form-template').innerHTML;
348 var tmpl = YUD.get('comment-inline-form-template').innerHTML;
332 tmpl = tmpl.format(f_path, line);
349 tmpl = tmpl.format(f_path, line);
@@ -337,12 +354,27 b' var createInlineForm = function(parent_t'
337 var form_hide_button = new YAHOO.util.Element(form.getElementsByClassName('hide-inline-form')[0]);
354 var form_hide_button = new YAHOO.util.Element(form.getElementsByClassName('hide-inline-form')[0]);
338 form_hide_button.on('click', function(e) {
355 form_hide_button.on('click', function(e) {
339 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
356 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
357 if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
358 YUD.setStyle(newtr.nextElementSibling,'display','');
359 }
340 removeInlineForm(newtr);
360 removeInlineForm(newtr);
341 YUD.removeClass(parent_tr, 'form-open');
361 YUD.removeClass(parent_tr, 'form-open');
362
342 });
363 });
364
343 return form
365 return form
344 };
366 };
367
368 /**
369 * Inject inline comment for on given TR this tr should be always an .line
370 * tr containing the line. Code will detect comment, and always put the comment
371 * block at the very bottom
372 */
345 var injectInlineForm = function(tr){
373 var injectInlineForm = function(tr){
374 if(!YUD.hasClass(tr, 'line')){
375 return
376 }
377 var submit_url = AJAX_COMMENT_URL;
346 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(tr,'no-comment')){
378 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(tr,'no-comment')){
347 return
379 return
348 }
380 }
@@ -350,20 +382,92 b' var injectInlineForm = function(tr){'
350 var node = tr.parentNode.parentNode.parentNode.getElementsByClassName('full_f_path')[0];
382 var node = tr.parentNode.parentNode.parentNode.getElementsByClassName('full_f_path')[0];
351 var f_path = YUD.getAttribute(node,'path');
383 var f_path = YUD.getAttribute(node,'path');
352 var lineno = getLineNo(tr);
384 var lineno = getLineNo(tr);
353 var form = createInlineForm(tr, f_path, lineno);
385 var form = createInlineForm(tr, f_path, lineno, submit_url);
354 var target_tr = tr;
386
355 if(YUD.hasClass(YUD.getNextSibling(tr),'inline-comments')){
387 var parent = tr;
356 target_tr = YUD.getNextSibling(tr);
388 while (1){
357 }
389 var n = parent.nextElementSibling;
358 YUD.insertAfter(form,target_tr);
390 // next element are comments !
391 if(YUD.hasClass(n,'inline-comments')){
392 parent = n;
393 }
394 else{
395 break;
396 }
397 }
398 YUD.insertAfter(form,parent);
399
359 YUD.get('text_'+lineno).focus();
400 YUD.get('text_'+lineno).focus();
401 var f = YUD.get(form);
402
403 var overlay = f.getElementsByClassName('overlay')[0];
404 var _form = f.getElementsByClassName('inline-form')[0];
405
406 form.on('submit',function(e){
407 YUE.preventDefault(e);
408
409 //ajax submit
410 var text = YUD.get('text_'+lineno).value;
411 var postData = {
412 'text':text,
413 'f_path':f_path,
414 'line':lineno
415 };
416
417 if(lineno === undefined){
418 alert('missing line !');
419 return
420 }
421 if(f_path === undefined){
422 alert('missing file path !');
423 return
424 }
425
426 var success = function(o){
427 YUD.removeClass(tr, 'form-open');
428 removeInlineForm(f);
429 var json_data = JSON.parse(o.responseText);
430 renderInlineComment(json_data);
431 };
432
433 if (YUD.hasClass(overlay,'overlay')){
434 var w = _form.offsetWidth;
435 var h = _form.offsetHeight;
436 YUD.setStyle(overlay,'width',w+'px');
437 YUD.setStyle(overlay,'height',h+'px');
438 }
439 YUD.addClass(overlay, 'submitting');
440
441 ajaxPOST(submit_url, postData, success);
442 });
443
360 tooltip_activate();
444 tooltip_activate();
361 };
445 };
362
446
363 var createInlineAddButton = function(tr,label){
447 var deleteComment = function(comment_id){
364 var html = '<div class="add-comment"><span class="ui-btn">{0}</span></div>'.format(label);
448 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
365
449 var postData = {'_method':'delete'};
366 var add = new YAHOO.util.Element(tableTr('inline-comments-button',html));
450 var success = function(o){
451 var n = YUD.get('comment-tr-'+comment_id);
452 var root = n.previousElementSibling.previousElementSibling;
453 n.parentNode.removeChild(n);
454
455 // scann nodes, and attach add button to last one
456 placeAddButton(root);
457 }
458 ajaxPOST(url,postData,success);
459 }
460
461
462 var createInlineAddButton = function(tr){
463
464 var label = TRANSLATION_MAP['add another comment'];
465
466 var html_el = document.createElement('div');
467 YUD.addClass(html_el, 'add-comment');
468 html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
469
470 var add = new YAHOO.util.Element(html_el);
367 add.on('click', function(e) {
471 add.on('click', function(e) {
368 injectInlineForm(tr);
472 injectInlineForm(tr);
369 });
473 });
@@ -384,6 +488,103 b' var getLineNo = function(tr) {'
384 return line
488 return line
385 };
489 };
386
490
491 var placeAddButton = function(target_tr){
492 if(!target_tr){
493 return
494 }
495 var last_node = target_tr;
496 //scann
497 while (1){
498 var n = last_node.nextElementSibling;
499 // next element are comments !
500 if(YUD.hasClass(n,'inline-comments')){
501 last_node = n;
502 //also remove the comment button from previos
503 var comment_add_buttons = last_node.getElementsByClassName('add-comment');
504 for(var i=0;i<comment_add_buttons.length;i++){
505 var b = comment_add_buttons[i];
506 b.parentNode.removeChild(b);
507 }
508 }
509 else{
510 break;
511 }
512 }
513
514 var add = createInlineAddButton(target_tr);
515 // get the comment div
516 var comment_block = last_node.getElementsByClassName('comment')[0];
517 // attach add button
518 YUD.insertAfter(add,comment_block);
519 }
520
521 /**
522 * Places the inline comment into the changeset block in proper line position
523 */
524 var placeInline = function(target_container,lineno,html){
525 var lineid = "{0}_{1}".format(target_container,lineno);
526 var target_line = YUD.get(lineid);
527 var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
528
529 // check if there are comments already !
530 var parent = target_line.parentNode;
531 var root_parent = parent;
532 while (1){
533 var n = parent.nextElementSibling;
534 // next element are comments !
535 if(YUD.hasClass(n,'inline-comments')){
536 parent = n;
537 }
538 else{
539 break;
540 }
541 }
542 // put in the comment at the bottom
543 YUD.insertAfter(comment,parent);
544
545 // scann nodes, and attach add button to last one
546 placeAddButton(root_parent);
547
548 return target_line;
549 }
550
551 /**
552 * make a single inline comment and place it inside
553 */
554 var renderInlineComment = function(json_data){
555 try{
556 var html = json_data['rendered_text'];
557 var lineno = json_data['line_no'];
558 var target_id = json_data['target_id'];
559 placeInline(target_id, lineno, html);
560
561 }catch(e){
562 console.log(e);
563 }
564 }
565
566 /**
567 * Iterates over all the inlines, and places them inside proper blocks of data
568 */
569 var renderInlineComments = function(file_comments){
570 for (f in file_comments){
571 // holding all comments for a FILE
572 var box = file_comments[f];
573
574 var target_id = YUD.getAttribute(box,'target_id');
575 // actually comments with line numbers
576 var comments = box.children;
577 for(var i=0; i<comments.length; i++){
578 var data = {
579 'rendered_text': comments[i].outerHTML,
580 'line_no': YUD.getAttribute(comments[i],'line'),
581 'target_id': target_id
582 }
583 renderInlineComment(data);
584 }
585 }
586 }
587
387
588
388 var fileBrowserListeners = function(current_url, node_list_url, url_base,
589 var fileBrowserListeners = function(current_url, node_list_url, url_base,
389 truncated_lbl, nomatch_lbl){
590 truncated_lbl, nomatch_lbl){
@@ -47,9 +47,13 b''
47
47
48 <script type="text/javascript">
48 <script type="text/javascript">
49 var follow_base_url = "${h.url('toggle_following')}";
49 var follow_base_url = "${h.url('toggle_following')}";
50 var stop_follow_text = "${_('Stop following this repository')}";
50
51 var start_follow_text = "${_('Start following this repository')}";
51 //JS translations map
52
52 var TRANSLATION_MAP = {
53 'add another comment':'${_("add another comment")}',
54 'Stop following this repository':"${_('Stop following this repository')}",
55 'Start following this repository':"${_('Start following this repository')}",
56 };
53
57
54 var onSuccessFollow = function(target){
58 var onSuccessFollow = function(target){
55 var f = YUD.get(target.id);
59 var f = YUD.get(target.id);
@@ -57,7 +61,7 b''
57
61
58 if(f.getAttribute('class')=='follow'){
62 if(f.getAttribute('class')=='follow'){
59 f.setAttribute('class','following');
63 f.setAttribute('class','following');
60 f.setAttribute('title',stop_follow_text);
64 f.setAttribute('title',TRANSLATION_MAP['Stop following this repository']);
61
65
62 if(f_cnt){
66 if(f_cnt){
63 var cnt = Number(f_cnt.innerHTML)+1;
67 var cnt = Number(f_cnt.innerHTML)+1;
@@ -66,7 +70,7 b''
66 }
70 }
67 else{
71 else{
68 f.setAttribute('class','follow');
72 f.setAttribute('class','follow');
69 f.setAttribute('title',start_follow_text);
73 f.setAttribute('title',TRANSLATION_MAP['Start following this repository']);
70 if(f_cnt){
74 if(f_cnt){
71 var cnt = Number(f_cnt.innerHTML)+1;
75 var cnt = Number(f_cnt.innerHTML)+1;
72 f_cnt.innerHTML = cnt;
76 f_cnt.innerHTML = cnt;
@@ -122,22 +122,12 b''
122 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
122 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
123 ${comment.comment_inline_form(c.changeset)}
123 ${comment.comment_inline_form(c.changeset)}
124
124
125 ## render comments
125 ${comment.comments(c.changeset)}
126 ${comment.comments(c.changeset)}
126
127 <script type="text/javascript">
127 <script type="text/javascript">
128 var deleteComment = function(comment_id){
129
130 var url = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}".replace('__COMMENT_ID__',comment_id);
131 var postData = '_method=delete';
132 var success = function(o){
133 var n = YUD.get('comment-'+comment_id);
134 n.parentNode.removeChild(n);
135 }
136 ajaxPOST(url,postData,success);
137 }
138
139 YUE.onDOMReady(function(){
128 YUE.onDOMReady(function(){
140
129 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
130 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}"
141 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
131 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
142 var show = 'none';
132 var show = 'none';
143 var target = e.currentTarget;
133 var target = e.currentTarget;
@@ -162,28 +152,7 b''
162
152
163 // inject comments into they proper positions
153 // inject comments into they proper positions
164 var file_comments = YUQ('.inline-comment-placeholder');
154 var file_comments = YUQ('.inline-comment-placeholder');
165
155 renderInlineComments(file_comments);
166 for (f in file_comments){
167 var box = file_comments[f];
168 var inlines = box.children;
169 for(var i=0; i<inlines.length; i++){
170 try{
171
172 var inline = inlines[i];
173 var lineno = YUD.getAttribute(inlines[i],'line');
174 var lineid = "{0}_{1}".format(YUD.getAttribute(inline,'target_id'),lineno);
175 var target_line = YUD.get(lineid);
176
177 var add = createInlineAddButton(target_line.parentNode,'${_("add another comment")}');
178 YUD.insertAfter(add,target_line.parentNode);
179
180 var comment = new YAHOO.util.Element(tableTr('inline-comments',inline.innerHTML))
181 YUD.insertAfter(comment,target_line.parentNode);
182 }catch(e){
183 console.log(e);
184 }
185 }
186 }
187 })
156 })
188
157
189 </script>
158 </script>
@@ -4,7 +4,7 b''
4 ## ${comment.comment_block(co)}
4 ## ${comment.comment_block(co)}
5 ##
5 ##
6 <%def name="comment_block(co)">
6 <%def name="comment_block(co)">
7 <div class="comment" id="comment-${co.comment_id}">
7 <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
8 <div class="comment-wrapp">
8 <div class="comment-wrapp">
9 <div class="meta">
9 <div class="meta">
10 <span class="user">
10 <span class="user">
@@ -32,7 +32,8 b''
32 <div id='comment-inline-form-template' style="display:none">
32 <div id='comment-inline-form-template' style="display:none">
33 <div class="comment-inline-form">
33 <div class="comment-inline-form">
34 %if c.rhodecode_user.username != 'default':
34 %if c.rhodecode_user.username != 'default':
35 ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id))}
35 <div class="overlay"><div class="overlay-text">${_('Submitting...')}</div></div>
36 ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id),class_='inline-form')}
36 <div class="clearfix">
37 <div class="clearfix">
37 <div class="comment-help">${_('Commenting on line')} {1}. ${_('Comments parsed using')}
38 <div class="comment-help">${_('Commenting on line')} {1}. ${_('Comments parsed using')}
38 <a href="${h.url('rst_help')}">RST</a> ${_('syntax')} ${_('with')}
39 <a href="${h.url('rst_help')}">RST</a> ${_('syntax')} ${_('with')}
@@ -43,7 +44,7 b''
43 <div class="comment-button">
44 <div class="comment-button">
44 <input type="hidden" name="f_path" value="{0}">
45 <input type="hidden" name="f_path" value="{0}">
45 <input type="hidden" name="line" value="{1}">
46 <input type="hidden" name="line" value="{1}">
46 ${h.submit('save', _('Comment'), class_='ui-btn')}
47 ${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
47 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
48 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
48 </div>
49 </div>
49 ${h.end_form()}
50 ${h.end_form()}
@@ -64,23 +65,27 b''
64 </%def>
65 </%def>
65
66
66
67
67 <%def name="comments(changeset)">
68 <%def name="inlines(changeset)">
68
69 <div class="comments">
70 <div class="comments-number">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
69 <div class="comments-number">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
71
72 %for path, lines in c.inline_comments:
70 %for path, lines in c.inline_comments:
73 <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.FID(changeset.raw_id,path)}">
74 % for line,comments in lines.iteritems():
71 % for line,comments in lines.iteritems():
75 <div class="inline-comment-placeholder-line" line="${line}" target_id="${h.safeid(h.safe_unicode(path))}">
72 <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
76 %for co in comments:
73 %for co in comments:
77 ${comment_block(co)}
74 ${comment_block(co)}
78 %endfor
75 %endfor
79 </div>
76 </div>
80 %endfor
77 %endfor
81 </div>
82 %endfor
78 %endfor
79
80 </%def>
83
81
82 <%def name="comments(changeset)">
83
84 <div class="comments">
85 <div id="inline-comments-container">
86 ${inlines(changeset)}
87 </div>
88
84 %for co in c.comments:
89 %for co in c.comments:
85 ${comment_block(co)}
90 ${comment_block(co)}
86 %endfor
91 %endfor
General Comments 0
You need to be logged in to leave comments. Login now