##// END OF EJS Templates
js: add check to eliminate js errors when inline comment function references an object which does not exist
lisaq -
r695:bbba5e47 default
parent child Browse files
Show More
@@ -1,655 +1,658 b''
1 1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 var firefoxAnchorFix = function() {
20 20 // hack to make anchor links behave properly on firefox, in our inline
21 21 // comments generation when comments are injected firefox is misbehaving
22 22 // when jumping to anchor links
23 23 if (location.href.indexOf('#') > -1) {
24 24 location.href += '';
25 25 }
26 26 };
27 27
28 28 // returns a node from given html;
29 29 var fromHTML = function(html){
30 30 var _html = document.createElement('element');
31 31 _html.innerHTML = html;
32 32 return _html;
33 33 };
34 34
35 35 var tableTr = function(cls, body){
36 36 var _el = document.createElement('div');
37 37 var _body = $(body).attr('id');
38 38 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
39 39 var id = 'comment-tr-{0}'.format(comment_id);
40 40 var _html = ('<table><tbody><tr id="{0}" class="{1}">'+
41 41 '<td class="add-comment-line"><span class="add-comment-content"></span></td>'+
42 42 '<td></td>'+
43 43 '<td></td>'+
44 44 '<td>{2}</td>'+
45 45 '</tr></tbody></table>').format(id, cls, body);
46 46 $(_el).html(_html);
47 47 return _el.children[0].children[0].children[0];
48 48 };
49 49
50 50 var removeInlineForm = function(form) {
51 51 form.parentNode.removeChild(form);
52 52 };
53 53
54 54 var createInlineForm = function(parent_tr, f_path, line) {
55 55 var tmpl = $('#comment-inline-form-template').html();
56 56 tmpl = tmpl.format(f_path, line);
57 57 var form = tableTr('comment-form-inline', tmpl);
58 58 var form_hide_button = $(form).find('.hide-inline-form');
59 59
60 60 $(form_hide_button).click(function(e) {
61 61 $('.inline-comments').removeClass('hide-comment-button');
62 62 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
63 63 if ($(newtr.nextElementSibling).hasClass('inline-comments-button')) {
64 64 $(newtr.nextElementSibling).show();
65 65 }
66 66 $(newtr).parents('.comment-form-inline').remove();
67 67 $(parent_tr).removeClass('form-open');
68 68 $(parent_tr).removeClass('hl-comment');
69 69 });
70 70
71 71 return form;
72 72 };
73 73
74 74 var getLineNo = function(tr) {
75 75 var line;
76 76 // Try to get the id and return "" (empty string) if it doesn't exist
77 77 var o = ($(tr).find('.lineno.old').attr('id')||"").split('_');
78 78 var n = ($(tr).find('.lineno.new').attr('id')||"").split('_');
79 79 if (n.length >= 2) {
80 80 line = n[n.length-1];
81 81 } else if (o.length >= 2) {
82 82 line = o[o.length-1];
83 83 }
84 84 return line;
85 85 };
86 86
87 87 /**
88 88 * make a single inline comment and place it inside
89 89 */
90 90 var renderInlineComment = function(json_data, show_add_button) {
91 91 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
92 92 try {
93 93 var html = json_data.rendered_text;
94 94 var lineno = json_data.line_no;
95 95 var target_id = json_data.target_id;
96 96 placeInline(target_id, lineno, html, show_add_button);
97 97 } catch (e) {
98 98 console.error(e);
99 99 }
100 100 };
101 101
102 102 function bindDeleteCommentButtons() {
103 103 $('.delete-comment').one('click', function() {
104 104 var comment_id = $(this).data("comment-id");
105 105
106 106 if (comment_id){
107 107 deleteComment(comment_id);
108 108 }
109 109 });
110 110 }
111 111
112 112 /**
113 113 * Inject inline comment for on given TR this tr should be always an .line
114 114 * tr containing the line. Code will detect comment, and always put the comment
115 115 * block at the very bottom
116 116 */
117 117 var injectInlineForm = function(tr){
118 118 if (!$(tr).hasClass('line')) {
119 119 return;
120 120 }
121 121
122 122 var _td = $(tr).find('.code').get(0);
123 123 if ($(tr).hasClass('form-open') ||
124 124 $(tr).hasClass('context') ||
125 125 $(_td).hasClass('no-comment')) {
126 126 return;
127 127 }
128 128 $(tr).addClass('form-open');
129 129 $(tr).addClass('hl-comment');
130 130 var node = $(tr.parentNode.parentNode.parentNode).find('.full_f_path').get(0);
131 131 var f_path = $(node).attr('path');
132 132 var lineno = getLineNo(tr);
133 133 var form = createInlineForm(tr, f_path, lineno);
134 134
135 135 var parent = tr;
136 136 while (1) {
137 137 var n = parent.nextElementSibling;
138 138 // next element are comments !
139 139 if ($(n).hasClass('inline-comments')) {
140 140 parent = n;
141 141 }
142 142 else {
143 143 break;
144 144 }
145 145 }
146 146 var _parent = $(parent).get(0);
147 147 $(_parent).after(form);
148 148 $('.comment-form-inline').prev('.inline-comments').addClass('hide-comment-button');
149 149 var f = $(form).get(0);
150 150
151 151 var _form = $(f).find('.inline-form').get(0);
152 152
153 153 var pullRequestId = templateContext.pull_request_data.pull_request_id;
154 154 var commitId = templateContext.commit_data.commit_id;
155 155
156 156 var commentForm = new CommentForm(_form, commitId, pullRequestId, lineno, false);
157 157 var cm = commentForm.getCmInstance();
158 158
159 159 // set a CUSTOM submit handler for inline comments.
160 160 commentForm.setHandleFormSubmit(function(o) {
161 161 var text = commentForm.cm.getValue();
162 162
163 163 if (text === "") {
164 164 return;
165 165 }
166 166
167 167 if (lineno === undefined) {
168 168 alert('missing line !');
169 169 return;
170 170 }
171 171 if (f_path === undefined) {
172 172 alert('missing file path !');
173 173 return;
174 174 }
175 175
176 176 var excludeCancelBtn = false;
177 177 var submitEvent = true;
178 178 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
179 179 commentForm.cm.setOption("readOnly", true);
180 180 var postData = {
181 181 'text': text,
182 182 'f_path': f_path,
183 183 'line': lineno,
184 184 'csrf_token': CSRF_TOKEN
185 185 };
186 186 var submitSuccessCallback = function(o) {
187 187 $(tr).removeClass('form-open');
188 188 removeInlineForm(f);
189 189 renderInlineComment(o);
190 190 $('.inline-comments').removeClass('hide-comment-button');
191 191
192 192 // re trigger the linkification of next/prev navigation
193 193 linkifyComments($('.inline-comment-injected'));
194 194 timeagoActivate();
195 195 bindDeleteCommentButtons();
196 196 commentForm.setActionButtonsDisabled(false);
197 197
198 198 };
199 199 var submitFailCallback = function(){
200 200 commentForm.resetCommentFormState(text)
201 201 };
202 202 commentForm.submitAjaxPOST(
203 203 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
204 204 });
205 205
206 206 setTimeout(function() {
207 207 // callbacks
208 208 if (cm !== undefined) {
209 209 cm.focus();
210 210 }
211 211 }, 10);
212 212
213 213 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
214 214 form:_form,
215 215 parent:_parent,
216 216 lineno: lineno,
217 217 f_path: f_path}
218 218 );
219 219 };
220 220
221 221 var deleteComment = function(comment_id) {
222 222 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
223 223 var postData = {
224 224 '_method': 'delete',
225 225 'csrf_token': CSRF_TOKEN
226 226 };
227 227
228 228 var success = function(o) {
229 229 window.location.reload();
230 230 };
231 231 ajaxPOST(url, postData, success);
232 232 };
233 233
234 234 var createInlineAddButton = function(tr){
235 235 var label = _gettext('Add another comment');
236 236 var html_el = document.createElement('div');
237 237 $(html_el).addClass('add-comment');
238 238 html_el.innerHTML = '<span class="btn btn-secondary">{0}</span>'.format(label);
239 239 var add = new $(html_el);
240 240 add.on('click', function(e) {
241 241 injectInlineForm(tr);
242 242 });
243 243 return add;
244 244 };
245 245
246 246 var placeAddButton = function(target_tr){
247 247 if(!target_tr){
248 248 return;
249 249 }
250 250 var last_node = target_tr;
251 251 // scan
252 252 while (1){
253 253 var n = last_node.nextElementSibling;
254 254 // next element are comments !
255 255 if($(n).hasClass('inline-comments')){
256 256 last_node = n;
257 257 // also remove the comment button from previous
258 258 var comment_add_buttons = $(last_node).find('.add-comment');
259 259 for(var i=0; i<comment_add_buttons.length; i++){
260 260 var b = comment_add_buttons[i];
261 261 b.parentNode.removeChild(b);
262 262 }
263 263 }
264 264 else{
265 265 break;
266 266 }
267 267 }
268 268 var add = createInlineAddButton(target_tr);
269 269 // get the comment div
270 270 var comment_block = $(last_node).find('.comment')[0];
271 271 // attach add button
272 272 $(add).insertAfter(comment_block);
273 273 };
274 274
275 275 /**
276 276 * Places the inline comment into the changeset block in proper line position
277 277 */
278 278 var placeInline = function(target_container, lineno, html, show_add_button) {
279 279 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
280 280
281 281 var lineid = "{0}_{1}".format(target_container, lineno);
282 282 var target_line = $('#' + lineid).get(0);
283 283 var comment = new $(tableTr('inline-comments', html));
284 284 // check if there are comments already !
285 var parent_node = target_line.parentNode;
286 var root_parent = parent_node;
287 while (1) {
288 var n = parent_node.nextElementSibling;
289 // next element are comments !
290 if ($(n).hasClass('inline-comments')) {
291 parent_node = n;
285 if (target_line) {
286 var parent_node = target_line.parentNode;
287 var root_parent = parent_node;
288
289 while (1) {
290 var n = parent_node.nextElementSibling;
291 // next element are comments !
292 if ($(n).hasClass('inline-comments')) {
293 parent_node = n;
294 }
295 else {
296 break;
297 }
292 298 }
293 else {
294 break;
299 // put in the comment at the bottom
300 $(comment).insertAfter(parent_node);
301 $(comment).find('.comment-inline').addClass('inline-comment-injected');
302 // scan nodes, and attach add button to last one
303 if (show_add_button) {
304 placeAddButton(root_parent);
295 305 }
296 306 }
297 // put in the comment at the bottom
298 $(comment).insertAfter(parent_node);
299 $(comment).find('.comment-inline').addClass('inline-comment-injected');
300 // scan nodes, and attach add button to last one
301 if (show_add_button) {
302 placeAddButton(root_parent);
303 }
304 307
305 308 return target_line;
306 309 };
307 310
308 311 var linkifyComments = function(comments) {
309 312
310 313 for (var i = 0; i < comments.length; i++) {
311 314 var comment_id = $(comments[i]).data('comment-id');
312 315 var prev_comment_id = $(comments[i - 1]).data('comment-id');
313 316 var next_comment_id = $(comments[i + 1]).data('comment-id');
314 317
315 318 // place next/prev links
316 319 if (prev_comment_id) {
317 320 $('#prev_c_' + comment_id).show();
318 321 $('#prev_c_' + comment_id + " a.arrow_comment_link").attr(
319 322 'href', '#comment-' + prev_comment_id).removeClass('disabled');
320 323 }
321 324 if (next_comment_id) {
322 325 $('#next_c_' + comment_id).show();
323 326 $('#next_c_' + comment_id + " a.arrow_comment_link").attr(
324 327 'href', '#comment-' + next_comment_id).removeClass('disabled');
325 328 }
326 329 // place a first link to the total counter
327 330 if (i === 0) {
328 331 $('#inline-comments-counter').attr('href', '#comment-' + comment_id);
329 332 }
330 333 }
331 334
332 335 };
333 336
334 337 /**
335 338 * Iterates over all the inlines, and places them inside proper blocks of data
336 339 */
337 340 var renderInlineComments = function(file_comments, show_add_button) {
338 341 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
339 342
340 343 for (var i = 0; i < file_comments.length; i++) {
341 344 var box = file_comments[i];
342 345
343 346 var target_id = $(box).attr('target_id');
344 347
345 348 // actually comments with line numbers
346 349 var comments = box.children;
347 350
348 351 for (var j = 0; j < comments.length; j++) {
349 352 var data = {
350 353 'rendered_text': comments[j].outerHTML,
351 354 'line_no': $(comments[j]).attr('line'),
352 355 'target_id': target_id
353 356 };
354 357 renderInlineComment(data, show_add_button);
355 358 }
356 359 }
357 360
358 361 // since order of injection is random, we're now re-iterating
359 362 // from correct order and filling in links
360 363 linkifyComments($('.inline-comment-injected'));
361 364 bindDeleteCommentButtons();
362 365 firefoxAnchorFix();
363 366 };
364 367
365 368
366 369 /* Comment form for main and inline comments */
367 370 var CommentForm = (function() {
368 371 "use strict";
369 372
370 373 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions) {
371 374
372 375 this.withLineNo = function(selector) {
373 376 var lineNo = this.lineNo;
374 377 if (lineNo === undefined) {
375 378 return selector
376 379 } else {
377 380 return selector + '_' + lineNo;
378 381 }
379 382 };
380 383
381 384 this.commitId = commitId;
382 385 this.pullRequestId = pullRequestId;
383 386 this.lineNo = lineNo;
384 387 this.initAutocompleteActions = initAutocompleteActions;
385 388
386 389 this.previewButton = this.withLineNo('#preview-btn');
387 390 this.previewContainer = this.withLineNo('#preview-container');
388 391
389 392 this.previewBoxSelector = this.withLineNo('#preview-box');
390 393
391 394 this.editButton = this.withLineNo('#edit-btn');
392 395 this.editContainer = this.withLineNo('#edit-container');
393 396
394 397 this.cancelButton = this.withLineNo('#cancel-btn');
395 398
396 399 this.statusChange = '#change_status';
397 400 this.cmBox = this.withLineNo('#text');
398 401 this.cm = initCommentBoxCodeMirror(this.cmBox, this.initAutocompleteActions);
399 402
400 403 this.submitForm = formElement;
401 404 this.submitButton = $(this.submitForm).find('input[type="submit"]');
402 405 this.submitButtonText = this.submitButton.val();
403 406
404 407 this.previewUrl = pyroutes.url('changeset_comment_preview',
405 408 {'repo_name': templateContext.repo_name});
406 409
407 410 // based on commitId, or pullReuqestId decide where do we submit
408 411 // out data
409 412 if (this.commitId){
410 413 this.submitUrl = pyroutes.url('changeset_comment',
411 414 {'repo_name': templateContext.repo_name,
412 415 'revision': this.commitId});
413 416
414 417 } else if (this.pullRequestId) {
415 418 this.submitUrl = pyroutes.url('pullrequest_comment',
416 419 {'repo_name': templateContext.repo_name,
417 420 'pull_request_id': this.pullRequestId});
418 421
419 422 } else {
420 423 throw new Error(
421 424 'CommentForm requires pullRequestId, or commitId to be specified.')
422 425 }
423 426
424 427 this.getCmInstance = function(){
425 428 return this.cm
426 429 };
427 430
428 431 var self = this;
429 432
430 433 this.getCommentStatus = function() {
431 434 return $(this.submitForm).find(this.statusChange).val();
432 435 };
433 436
434 437 this.isAllowedToSubmit = function() {
435 438 return !$(this.submitButton).prop('disabled');
436 439 };
437 440
438 441 this.initStatusChangeSelector = function(){
439 442 var formatChangeStatus = function(state, escapeMarkup) {
440 443 var originalOption = state.element;
441 444 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
442 445 '<span>' + escapeMarkup(state.text) + '</span>';
443 446 };
444 447 var formatResult = function(result, container, query, escapeMarkup) {
445 448 return formatChangeStatus(result, escapeMarkup);
446 449 };
447 450
448 451 var formatSelection = function(data, container, escapeMarkup) {
449 452 return formatChangeStatus(data, escapeMarkup);
450 453 };
451 454
452 455 $(this.submitForm).find(this.statusChange).select2({
453 456 placeholder: _gettext('Status Review'),
454 457 formatResult: formatResult,
455 458 formatSelection: formatSelection,
456 459 containerCssClass: "drop-menu status_box_menu",
457 460 dropdownCssClass: "drop-menu-dropdown",
458 461 dropdownAutoWidth: true,
459 462 minimumResultsForSearch: -1
460 463 });
461 464 $(this.submitForm).find(this.statusChange).on('change', function() {
462 465 var status = self.getCommentStatus();
463 466 if (status && !self.lineNo) {
464 467 $(self.submitButton).prop('disabled', false);
465 468 }
466 469 //todo, fix this name
467 470 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
468 471 self.cm.setOption('placeholder', placeholderText);
469 472 })
470 473 };
471 474
472 475 // reset the comment form into it's original state
473 476 this.resetCommentFormState = function(content) {
474 477 content = content || '';
475 478
476 479 $(this.editContainer).show();
477 480 $(this.editButton).hide();
478 481
479 482 $(this.previewContainer).hide();
480 483 $(this.previewButton).show();
481 484
482 485 this.setActionButtonsDisabled(true);
483 486 self.cm.setValue(content);
484 487 self.cm.setOption("readOnly", false);
485 488 };
486 489
487 490 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
488 491 failHandler = failHandler || function() {};
489 492 var postData = toQueryString(postData);
490 493 var request = $.ajax({
491 494 url: url,
492 495 type: 'POST',
493 496 data: postData,
494 497 headers: {'X-PARTIAL-XHR': true}
495 498 })
496 499 .done(function(data) {
497 500 successHandler(data);
498 501 })
499 502 .fail(function(data, textStatus, errorThrown){
500 503 alert(
501 504 "Error while submitting comment.\n" +
502 505 "Error code {0} ({1}).".format(data.status, data.statusText));
503 506 failHandler()
504 507 });
505 508 return request;
506 509 };
507 510
508 511 // overwrite a submitHandler, we need to do it for inline comments
509 512 this.setHandleFormSubmit = function(callback) {
510 513 this.handleFormSubmit = callback;
511 514 };
512 515
513 516 // default handler for for submit for main comments
514 517 this.handleFormSubmit = function() {
515 518 var text = self.cm.getValue();
516 519 var status = self.getCommentStatus();
517 520
518 521 if (text === "" && !status) {
519 522 return;
520 523 }
521 524
522 525 var excludeCancelBtn = false;
523 526 var submitEvent = true;
524 527 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
525 528 self.cm.setOption("readOnly", true);
526 529 var postData = {
527 530 'text': text,
528 531 'changeset_status': status,
529 532 'csrf_token': CSRF_TOKEN
530 533 };
531 534
532 535 var submitSuccessCallback = function(o) {
533 536 if (status) {
534 537 location.reload(true);
535 538 } else {
536 539 $('#injected_page_comments').append(o.rendered_text);
537 540 self.resetCommentFormState();
538 541 bindDeleteCommentButtons();
539 542 timeagoActivate();
540 543 }
541 544 };
542 545 var submitFailCallback = function(){
543 546 self.resetCommentFormState(text)
544 547 };
545 548 self.submitAjaxPOST(
546 549 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
547 550 };
548 551
549 552 this.previewSuccessCallback = function(o) {
550 553 $(self.previewBoxSelector).html(o);
551 554 $(self.previewBoxSelector).removeClass('unloaded');
552 555
553 556 // swap buttons
554 557 $(self.previewButton).hide();
555 558 $(self.editButton).show();
556 559
557 560 // unlock buttons
558 561 self.setActionButtonsDisabled(false);
559 562 };
560 563
561 564 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
562 565 excludeCancelBtn = excludeCancelBtn || false;
563 566 submitEvent = submitEvent || false;
564 567
565 568 $(this.editButton).prop('disabled', state);
566 569 $(this.previewButton).prop('disabled', state);
567 570
568 571 if (!excludeCancelBtn) {
569 572 $(this.cancelButton).prop('disabled', state);
570 573 }
571 574
572 575 var submitState = state;
573 576 if (!submitEvent && this.getCommentStatus() && !this.lineNo) {
574 577 // if the value of commit review status is set, we allow
575 578 // submit button, but only on Main form, lineNo means inline
576 579 submitState = false
577 580 }
578 581 $(this.submitButton).prop('disabled', submitState);
579 582 if (submitEvent) {
580 583 $(this.submitButton).val(_gettext('Submitting...'));
581 584 } else {
582 585 $(this.submitButton).val(this.submitButtonText);
583 586 }
584 587
585 588 };
586 589
587 590 // lock preview/edit/submit buttons on load, but exclude cancel button
588 591 var excludeCancelBtn = true;
589 592 this.setActionButtonsDisabled(true, excludeCancelBtn);
590 593
591 594 // anonymous users don't have access to initialized CM instance
592 595 if (this.cm !== undefined){
593 596 this.cm.on('change', function(cMirror) {
594 597 if (cMirror.getValue() === "") {
595 598 self.setActionButtonsDisabled(true, excludeCancelBtn)
596 599 } else {
597 600 self.setActionButtonsDisabled(false, excludeCancelBtn)
598 601 }
599 602 });
600 603 }
601 604
602 605 $(this.editButton).on('click', function(e) {
603 606 e.preventDefault();
604 607
605 608 $(self.previewButton).show();
606 609 $(self.previewContainer).hide();
607 610 $(self.editButton).hide();
608 611 $(self.editContainer).show();
609 612
610 613 });
611 614
612 615 $(this.previewButton).on('click', function(e) {
613 616 e.preventDefault();
614 617 var text = self.cm.getValue();
615 618
616 619 if (text === "") {
617 620 return;
618 621 }
619 622
620 623 var postData = {
621 624 'text': text,
622 625 'renderer': DEFAULT_RENDERER,
623 626 'csrf_token': CSRF_TOKEN
624 627 };
625 628
626 629 // lock ALL buttons on preview
627 630 self.setActionButtonsDisabled(true);
628 631
629 632 $(self.previewBoxSelector).addClass('unloaded');
630 633 $(self.previewBoxSelector).html(_gettext('Loading ...'));
631 634 $(self.editContainer).hide();
632 635 $(self.previewContainer).show();
633 636
634 637 // by default we reset state of comment preserving the text
635 638 var previewFailCallback = function(){
636 639 self.resetCommentFormState(text)
637 640 };
638 641 self.submitAjaxPOST(
639 642 self.previewUrl, postData, self.previewSuccessCallback, previewFailCallback);
640 643
641 644 });
642 645
643 646 $(this.submitForm).submit(function(e) {
644 647 e.preventDefault();
645 648 var allowedToSubmit = self.isAllowedToSubmit();
646 649 if (!allowedToSubmit){
647 650 return false;
648 651 }
649 652 self.handleFormSubmit();
650 653 });
651 654
652 655 }
653 656
654 657 return CommentForm;
655 658 })();
General Comments 0
You need to be logged in to leave comments. Login now