// # Copyright (C) 2010-2016 RhodeCode GmbH // # // # This program is free software: you can redistribute it and/or modify // # it under the terms of the GNU Affero General Public License, version 3 // # (only), as published by the Free Software Foundation. // # // # This program is distributed in the hope that it will be useful, // # but WITHOUT ANY WARRANTY; without even the implied warranty of // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // # GNU General Public License for more details. // # // # You should have received a copy of the GNU Affero General Public License // # along with this program. If not, see . // # // # This program is dual-licensed. If you wish to learn more about the // # RhodeCode Enterprise Edition, including its added features, Support services, // # and proprietary license terms, please see https://rhodecode.com/licenses/ var firefoxAnchorFix = function() { // hack to make anchor links behave properly on firefox, in our inline // comments generation when comments are injected firefox is misbehaving // when jumping to anchor links if (location.href.indexOf('#') > -1) { location.href += ''; } }; // returns a node from given html; var fromHTML = function(html){ var _html = document.createElement('element'); _html.innerHTML = html; return _html; }; var tableTr = function(cls, body){ var _el = document.createElement('div'); var _body = $(body).attr('id'); var comment_id = fromHTML(body).children[0].id.split('comment-')[1]; var id = 'comment-tr-{0}'.format(comment_id); var _html = (''+ ''+ ''+ ''+ ''+ '
{2}
').format(id, cls, body); $(_el).html(_html); return _el.children[0].children[0].children[0]; }; var removeInlineForm = function(form) { form.parentNode.removeChild(form); }; var createInlineForm = function(parent_tr, f_path, line) { var tmpl = $('#comment-inline-form-template').html(); tmpl = tmpl.format(f_path, line); var form = tableTr('comment-form-inline', tmpl); var form_hide_button = $(form).find('.hide-inline-form'); $(form_hide_button).click(function(e) { $('.inline-comments').removeClass('hide-comment-button'); var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode; if ($(newtr.nextElementSibling).hasClass('inline-comments-button')) { $(newtr.nextElementSibling).show(); } $(newtr).parents('.comment-form-inline').remove(); $(parent_tr).removeClass('form-open'); $(parent_tr).removeClass('hl-comment'); }); return form; }; var getLineNo = function(tr) { var line; // Try to get the id and return "" (empty string) if it doesn't exist var o = ($(tr).find('.lineno.old').attr('id')||"").split('_'); var n = ($(tr).find('.lineno.new').attr('id')||"").split('_'); if (n.length >= 2) { line = n[n.length-1]; } else if (o.length >= 2) { line = o[o.length-1]; } return line; }; /** * make a single inline comment and place it inside */ var renderInlineComment = function(json_data, show_add_button) { show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true; 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, show_add_button); } catch (e) { console.error(e); } }; function bindDeleteCommentButtons() { $('.delete-comment').one('click', function() { var comment_id = $(this).data("comment-id"); if (comment_id){ deleteComment(comment_id); } }); } /** * 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 (!$(tr).hasClass('line')) { return; } var _td = $(tr).find('.code').get(0); if ($(tr).hasClass('form-open') || $(tr).hasClass('context') || $(_td).hasClass('no-comment')) { return; } $(tr).addClass('form-open'); $(tr).addClass('hl-comment'); var node = $(tr.parentNode.parentNode.parentNode).find('.full_f_path').get(0); var f_path = $(node).attr('path'); var lineno = getLineNo(tr); var form = createInlineForm(tr, f_path, lineno); var parent = tr; while (1) { var n = parent.nextElementSibling; // next element are comments ! if ($(n).hasClass('inline-comments')) { parent = n; } else { break; } } var _parent = $(parent).get(0); $(_parent).after(form); $('.comment-form-inline').prev('.inline-comments').addClass('hide-comment-button'); var f = $(form).get(0); var _form = $(f).find('.inline-form').get(0); var pullRequestId = templateContext.pull_request_data.pull_request_id; var commitId = templateContext.commit_data.commit_id; 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(o) { $(tr).removeClass('form-open'); removeInlineForm(f); renderInlineComment(o); $('.inline-comments').removeClass('hide-comment-button'); // 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:_parent, lineno: lineno, f_path: f_path} ); }; var deleteComment = function(comment_id) { var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id); var postData = { '_method': 'delete', 'csrf_token': CSRF_TOKEN }; var success = function(o) { window.location.reload(); }; ajaxPOST(url, postData, success); }; var createInlineAddButton = function(tr){ var label = _gettext('Add another comment'); var html_el = document.createElement('div'); $(html_el).addClass('add-comment'); html_el.innerHTML = '{0}'.format(label); var add = new $(html_el); add.on('click', function(e) { injectInlineForm(tr); }); return add; }; var placeAddButton = function(target_tr){ if(!target_tr){ return; } var last_node = target_tr; // scan while (1){ var n = last_node.nextElementSibling; // next element are comments ! if($(n).hasClass('inline-comments')){ last_node = n; // also remove the comment button from previous var comment_add_buttons = $(last_node).find('.add-comment'); for(var i=0; i' + '' + escapeMarkup(state.text) + ''; }; var formatResult = function(result, container, query, escapeMarkup) { return formatChangeStatus(result, escapeMarkup); }; var formatSelection = function(data, container, escapeMarkup) { return formatChangeStatus(data, escapeMarkup); }; $(this.submitForm).find(this.statusChange).select2({ placeholder: _gettext('Status Review'), formatResult: formatResult, formatSelection: formatSelection, containerCssClass: "drop-menu status_box_menu", dropdownCssClass: "drop-menu-dropdown", dropdownAutoWidth: true, minimumResultsForSearch: -1 }); $(this.submitForm).find(this.statusChange).on('change', function() { var status = self.getCommentStatus(); if (status && !self.lineNo) { $(self.submitButton).prop('disabled', false); } //todo, fix this name var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status); self.cm.setOption('placeholder', placeholderText); }) }; // reset the comment form into it's original state this.resetCommentFormState = function(content) { content = content || ''; $(this.editContainer).show(); $(this.editButton).hide(); $(this.previewContainer).hide(); $(this.previewButton).show(); this.setActionButtonsDisabled(true); self.cm.setValue(content); self.cm.setOption("readOnly", false); }; this.submitAjaxPOST = function(url, postData, successHandler, failHandler) { failHandler = failHandler || function() {}; var postData = toQueryString(postData); var request = $.ajax({ url: url, type: 'POST', data: postData, headers: {'X-PARTIAL-XHR': true} }) .done(function(data) { successHandler(data); }) .fail(function(data, textStatus, errorThrown){ alert( "Error while submitting comment.\n" + "Error code {0} ({1}).".format(data.status, data.statusText)); failHandler() }); return request; }; // overwrite a submitHandler, we need to do it for inline comments this.setHandleFormSubmit = function(callback) { this.handleFormSubmit = callback; }; // default handler for for submit for main comments this.handleFormSubmit = function() { var text = self.cm.getValue(); var status = self.getCommentStatus(); if (text === "" && !status) { return; } var excludeCancelBtn = false; var submitEvent = true; self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent); self.cm.setOption("readOnly", true); var postData = { 'text': text, 'changeset_status': status, 'csrf_token': CSRF_TOKEN }; var submitSuccessCallback = function(o) { if (status) { location.reload(true); } else { $('#injected_page_comments').append(o.rendered_text); self.resetCommentFormState(); bindDeleteCommentButtons(); timeagoActivate(); } }; var submitFailCallback = function(){ self.resetCommentFormState(text) }; self.submitAjaxPOST( self.submitUrl, postData, submitSuccessCallback, submitFailCallback); }; this.previewSuccessCallback = function(o) { $(self.previewBoxSelector).html(o); $(self.previewBoxSelector).removeClass('unloaded'); // swap buttons $(self.previewButton).hide(); $(self.editButton).show(); // unlock buttons self.setActionButtonsDisabled(false); }; this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) { excludeCancelBtn = excludeCancelBtn || false; submitEvent = submitEvent || false; $(this.editButton).prop('disabled', state); $(this.previewButton).prop('disabled', state); if (!excludeCancelBtn) { $(this.cancelButton).prop('disabled', state); } var submitState = state; if (!submitEvent && this.getCommentStatus() && !this.lineNo) { // if the value of commit review status is set, we allow // submit button, but only on Main form, lineNo means inline submitState = false } $(this.submitButton).prop('disabled', submitState); if (submitEvent) { $(this.submitButton).val(_gettext('Submitting...')); } else { $(this.submitButton).val(this.submitButtonText); } }; // lock preview/edit/submit buttons on load, but exclude cancel button var excludeCancelBtn = true; this.setActionButtonsDisabled(true, excludeCancelBtn); // anonymous users don't have access to initialized CM instance if (this.cm !== undefined){ this.cm.on('change', function(cMirror) { if (cMirror.getValue() === "") { self.setActionButtonsDisabled(true, excludeCancelBtn) } else { self.setActionButtonsDisabled(false, excludeCancelBtn) } }); } $(this.editButton).on('click', function(e) { e.preventDefault(); $(self.previewButton).show(); $(self.previewContainer).hide(); $(self.editButton).hide(); $(self.editContainer).show(); }); $(this.previewButton).on('click', function(e) { e.preventDefault(); var text = self.cm.getValue(); if (text === "") { return; } var postData = { 'text': text, 'renderer': DEFAULT_RENDERER, 'csrf_token': CSRF_TOKEN }; // lock ALL buttons on preview self.setActionButtonsDisabled(true); $(self.previewBoxSelector).addClass('unloaded'); $(self.previewBoxSelector).html(_gettext('Loading ...')); $(self.editContainer).hide(); $(self.previewContainer).show(); // by default we reset state of comment preserving the text var previewFailCallback = function(){ self.resetCommentFormState(text) }; self.submitAjaxPOST( self.previewUrl, postData, self.previewSuccessCallback, previewFailCallback); }); $(this.submitForm).submit(function(e) { e.preventDefault(); var allowedToSubmit = self.isAllowedToSubmit(); if (!allowedToSubmit){ return false; } self.handleFormSubmit(); }); } return CommentForm; })();