##// END OF EJS Templates
pull-requests: fixed some xss problems with odd filenames.
milka -
r4652:9012cc2f default
parent child Browse files
Show More
@@ -1,1639 +1,1639 b''
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 var firefoxAnchorFix = function() {
19 var firefoxAnchorFix = function() {
20 // hack to make anchor links behave properly on firefox, in our inline
20 // hack to make anchor links behave properly on firefox, in our inline
21 // comments generation when comments are injected firefox is misbehaving
21 // comments generation when comments are injected firefox is misbehaving
22 // when jumping to anchor links
22 // when jumping to anchor links
23 if (location.href.indexOf('#') > -1) {
23 if (location.href.indexOf('#') > -1) {
24 location.href += '';
24 location.href += '';
25 }
25 }
26 };
26 };
27
27
28
28
29 var linkifyComments = function(comments) {
29 var linkifyComments = function(comments) {
30 var firstCommentId = null;
30 var firstCommentId = null;
31 if (comments) {
31 if (comments) {
32 firstCommentId = $(comments[0]).data('comment-id');
32 firstCommentId = $(comments[0]).data('comment-id');
33 }
33 }
34
34
35 if (firstCommentId){
35 if (firstCommentId){
36 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
36 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
37 }
37 }
38 };
38 };
39
39
40
40
41 var bindToggleButtons = function() {
41 var bindToggleButtons = function() {
42 $('.comment-toggle').on('click', function() {
42 $('.comment-toggle').on('click', function() {
43 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
43 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
44 });
44 });
45 };
45 };
46
46
47
47
48 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
48 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
49 failHandler = failHandler || function() {};
49 failHandler = failHandler || function() {};
50 postData = toQueryString(postData);
50 postData = toQueryString(postData);
51 var request = $.ajax({
51 var request = $.ajax({
52 url: url,
52 url: url,
53 type: 'POST',
53 type: 'POST',
54 data: postData,
54 data: postData,
55 headers: {'X-PARTIAL-XHR': true}
55 headers: {'X-PARTIAL-XHR': true}
56 })
56 })
57 .done(function (data) {
57 .done(function (data) {
58 successHandler(data);
58 successHandler(data);
59 })
59 })
60 .fail(function (data, textStatus, errorThrown) {
60 .fail(function (data, textStatus, errorThrown) {
61 failHandler(data, textStatus, errorThrown)
61 failHandler(data, textStatus, errorThrown)
62 });
62 });
63 return request;
63 return request;
64 };
64 };
65
65
66
66
67 /* Comment form for main and inline comments */
67 /* Comment form for main and inline comments */
68 (function(mod) {
68 (function(mod) {
69
69
70 if (typeof exports == "object" && typeof module == "object") {
70 if (typeof exports == "object" && typeof module == "object") {
71 // CommonJS
71 // CommonJS
72 module.exports = mod();
72 module.exports = mod();
73 }
73 }
74 else {
74 else {
75 // Plain browser env
75 // Plain browser env
76 (this || window).CommentForm = mod();
76 (this || window).CommentForm = mod();
77 }
77 }
78
78
79 })(function() {
79 })(function() {
80 "use strict";
80 "use strict";
81
81
82 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id) {
82 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id) {
83
83
84 if (!(this instanceof CommentForm)) {
84 if (!(this instanceof CommentForm)) {
85 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id);
85 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id);
86 }
86 }
87
87
88 // bind the element instance to our Form
88 // bind the element instance to our Form
89 $(formElement).get(0).CommentForm = this;
89 $(formElement).get(0).CommentForm = this;
90
90
91 this.withLineNo = function(selector) {
91 this.withLineNo = function(selector) {
92 var lineNo = this.lineNo;
92 var lineNo = this.lineNo;
93 if (lineNo === undefined) {
93 if (lineNo === undefined) {
94 return selector
94 return selector
95 } else {
95 } else {
96 return selector + '_' + lineNo;
96 return selector + '_' + lineNo;
97 }
97 }
98 };
98 };
99
99
100 this.commitId = commitId;
100 this.commitId = commitId;
101 this.pullRequestId = pullRequestId;
101 this.pullRequestId = pullRequestId;
102 this.lineNo = lineNo;
102 this.lineNo = lineNo;
103 this.initAutocompleteActions = initAutocompleteActions;
103 this.initAutocompleteActions = initAutocompleteActions;
104
104
105 this.previewButton = this.withLineNo('#preview-btn');
105 this.previewButton = this.withLineNo('#preview-btn');
106 this.previewContainer = this.withLineNo('#preview-container');
106 this.previewContainer = this.withLineNo('#preview-container');
107
107
108 this.previewBoxSelector = this.withLineNo('#preview-box');
108 this.previewBoxSelector = this.withLineNo('#preview-box');
109
109
110 this.editButton = this.withLineNo('#edit-btn');
110 this.editButton = this.withLineNo('#edit-btn');
111 this.editContainer = this.withLineNo('#edit-container');
111 this.editContainer = this.withLineNo('#edit-container');
112 this.cancelButton = this.withLineNo('#cancel-btn');
112 this.cancelButton = this.withLineNo('#cancel-btn');
113 this.commentType = this.withLineNo('#comment_type');
113 this.commentType = this.withLineNo('#comment_type');
114
114
115 this.resolvesId = null;
115 this.resolvesId = null;
116 this.resolvesActionId = null;
116 this.resolvesActionId = null;
117
117
118 this.closesPr = '#close_pull_request';
118 this.closesPr = '#close_pull_request';
119
119
120 this.cmBox = this.withLineNo('#text');
120 this.cmBox = this.withLineNo('#text');
121 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
121 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
122
122
123 this.statusChange = this.withLineNo('#change_status');
123 this.statusChange = this.withLineNo('#change_status');
124
124
125 this.submitForm = formElement;
125 this.submitForm = formElement;
126
126
127 this.submitButton = $(this.submitForm).find('.submit-comment-action');
127 this.submitButton = $(this.submitForm).find('.submit-comment-action');
128 this.submitButtonText = this.submitButton.val();
128 this.submitButtonText = this.submitButton.val();
129
129
130 this.submitDraftButton = $(this.submitForm).find('.submit-draft-action');
130 this.submitDraftButton = $(this.submitForm).find('.submit-draft-action');
131 this.submitDraftButtonText = this.submitDraftButton.val();
131 this.submitDraftButtonText = this.submitDraftButton.val();
132
132
133 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
133 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
134 {'repo_name': templateContext.repo_name,
134 {'repo_name': templateContext.repo_name,
135 'commit_id': templateContext.commit_data.commit_id});
135 'commit_id': templateContext.commit_data.commit_id});
136
136
137 if (edit){
137 if (edit){
138 this.submitDraftButton.hide();
138 this.submitDraftButton.hide();
139 this.submitButtonText = _gettext('Update Comment');
139 this.submitButtonText = _gettext('Update Comment');
140 $(this.commentType).prop('disabled', true);
140 $(this.commentType).prop('disabled', true);
141 $(this.commentType).addClass('disabled');
141 $(this.commentType).addClass('disabled');
142 var editInfo =
142 var editInfo =
143 '';
143 '';
144 $(editInfo).insertBefore($(this.editButton).parent());
144 $(editInfo).insertBefore($(this.editButton).parent());
145 }
145 }
146
146
147 if (resolvesCommentId){
147 if (resolvesCommentId){
148 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
148 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
149 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
149 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
150 $(this.commentType).prop('disabled', true);
150 $(this.commentType).prop('disabled', true);
151 $(this.commentType).addClass('disabled');
151 $(this.commentType).addClass('disabled');
152
152
153 // disable select
153 // disable select
154 setTimeout(function() {
154 setTimeout(function() {
155 $(self.statusChange).select2('readonly', true);
155 $(self.statusChange).select2('readonly', true);
156 }, 10);
156 }, 10);
157
157
158 var resolvedInfo = (
158 var resolvedInfo = (
159 '<li class="resolve-action">' +
159 '<li class="resolve-action">' +
160 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
160 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
161 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
161 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
162 '</li>'
162 '</li>'
163 ).format(resolvesCommentId, _gettext('resolve comment'));
163 ).format(resolvesCommentId, _gettext('resolve comment'));
164 $(resolvedInfo).insertAfter($(this.commentType).parent());
164 $(resolvedInfo).insertAfter($(this.commentType).parent());
165 }
165 }
166
166
167 // based on commitId, or pullRequestId decide where do we submit
167 // based on commitId, or pullRequestId decide where do we submit
168 // out data
168 // out data
169 if (this.commitId){
169 if (this.commitId){
170 var pyurl = 'repo_commit_comment_create';
170 var pyurl = 'repo_commit_comment_create';
171 if(edit){
171 if(edit){
172 pyurl = 'repo_commit_comment_edit';
172 pyurl = 'repo_commit_comment_edit';
173 }
173 }
174 this.submitUrl = pyroutes.url(pyurl,
174 this.submitUrl = pyroutes.url(pyurl,
175 {'repo_name': templateContext.repo_name,
175 {'repo_name': templateContext.repo_name,
176 'commit_id': this.commitId,
176 'commit_id': this.commitId,
177 'comment_id': comment_id});
177 'comment_id': comment_id});
178 this.selfUrl = pyroutes.url('repo_commit',
178 this.selfUrl = pyroutes.url('repo_commit',
179 {'repo_name': templateContext.repo_name,
179 {'repo_name': templateContext.repo_name,
180 'commit_id': this.commitId});
180 'commit_id': this.commitId});
181
181
182 } else if (this.pullRequestId) {
182 } else if (this.pullRequestId) {
183 var pyurl = 'pullrequest_comment_create';
183 var pyurl = 'pullrequest_comment_create';
184 if(edit){
184 if(edit){
185 pyurl = 'pullrequest_comment_edit';
185 pyurl = 'pullrequest_comment_edit';
186 }
186 }
187 this.submitUrl = pyroutes.url(pyurl,
187 this.submitUrl = pyroutes.url(pyurl,
188 {'repo_name': templateContext.repo_name,
188 {'repo_name': templateContext.repo_name,
189 'pull_request_id': this.pullRequestId,
189 'pull_request_id': this.pullRequestId,
190 'comment_id': comment_id});
190 'comment_id': comment_id});
191 this.selfUrl = pyroutes.url('pullrequest_show',
191 this.selfUrl = pyroutes.url('pullrequest_show',
192 {'repo_name': templateContext.repo_name,
192 {'repo_name': templateContext.repo_name,
193 'pull_request_id': this.pullRequestId});
193 'pull_request_id': this.pullRequestId});
194
194
195 } else {
195 } else {
196 throw new Error(
196 throw new Error(
197 'CommentForm requires pullRequestId, or commitId to be specified.')
197 'CommentForm requires pullRequestId, or commitId to be specified.')
198 }
198 }
199
199
200 // FUNCTIONS and helpers
200 // FUNCTIONS and helpers
201 var self = this;
201 var self = this;
202
202
203 this.isInline = function(){
203 this.isInline = function(){
204 return this.lineNo && this.lineNo != 'general';
204 return this.lineNo && this.lineNo != 'general';
205 };
205 };
206
206
207 this.getCmInstance = function(){
207 this.getCmInstance = function(){
208 return this.cm
208 return this.cm
209 };
209 };
210
210
211 this.setPlaceholder = function(placeholder) {
211 this.setPlaceholder = function(placeholder) {
212 var cm = this.getCmInstance();
212 var cm = this.getCmInstance();
213 if (cm){
213 if (cm){
214 cm.setOption('placeholder', placeholder);
214 cm.setOption('placeholder', placeholder);
215 }
215 }
216 };
216 };
217
217
218 this.getCommentStatus = function() {
218 this.getCommentStatus = function() {
219 return $(this.submitForm).find(this.statusChange).val();
219 return $(this.submitForm).find(this.statusChange).val();
220 };
220 };
221
221
222 this.getCommentType = function() {
222 this.getCommentType = function() {
223 return $(this.submitForm).find(this.commentType).val();
223 return $(this.submitForm).find(this.commentType).val();
224 };
224 };
225
225
226 this.getDraftState = function () {
226 this.getDraftState = function () {
227 var submitterElem = $(this.submitForm).find('input[type="submit"].submitter');
227 var submitterElem = $(this.submitForm).find('input[type="submit"].submitter');
228 var data = $(submitterElem).data('isDraft');
228 var data = $(submitterElem).data('isDraft');
229 return data
229 return data
230 }
230 }
231
231
232 this.getResolvesId = function() {
232 this.getResolvesId = function() {
233 return $(this.submitForm).find(this.resolvesId).val() || null;
233 return $(this.submitForm).find(this.resolvesId).val() || null;
234 };
234 };
235
235
236 this.getClosePr = function() {
236 this.getClosePr = function() {
237 return $(this.submitForm).find(this.closesPr).val() || null;
237 return $(this.submitForm).find(this.closesPr).val() || null;
238 };
238 };
239
239
240 this.markCommentResolved = function(resolvedCommentId){
240 this.markCommentResolved = function(resolvedCommentId){
241 Rhodecode.comments.markCommentResolved(resolvedCommentId)
241 Rhodecode.comments.markCommentResolved(resolvedCommentId)
242 };
242 };
243
243
244 this.isAllowedToSubmit = function() {
244 this.isAllowedToSubmit = function() {
245 var commentDisabled = $(this.submitButton).prop('disabled');
245 var commentDisabled = $(this.submitButton).prop('disabled');
246 var draftDisabled = $(this.submitDraftButton).prop('disabled');
246 var draftDisabled = $(this.submitDraftButton).prop('disabled');
247 return !commentDisabled && !draftDisabled;
247 return !commentDisabled && !draftDisabled;
248 };
248 };
249
249
250 this.initStatusChangeSelector = function(){
250 this.initStatusChangeSelector = function(){
251 var formatChangeStatus = function(state, escapeMarkup) {
251 var formatChangeStatus = function(state, escapeMarkup) {
252 var originalOption = state.element;
252 var originalOption = state.element;
253 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
253 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
254 return tmpl
254 return tmpl
255 };
255 };
256 var formatResult = function(result, container, query, escapeMarkup) {
256 var formatResult = function(result, container, query, escapeMarkup) {
257 return formatChangeStatus(result, escapeMarkup);
257 return formatChangeStatus(result, escapeMarkup);
258 };
258 };
259
259
260 var formatSelection = function(data, container, escapeMarkup) {
260 var formatSelection = function(data, container, escapeMarkup) {
261 return formatChangeStatus(data, escapeMarkup);
261 return formatChangeStatus(data, escapeMarkup);
262 };
262 };
263
263
264 $(this.submitForm).find(this.statusChange).select2({
264 $(this.submitForm).find(this.statusChange).select2({
265 placeholder: _gettext('Status Review'),
265 placeholder: _gettext('Status Review'),
266 formatResult: formatResult,
266 formatResult: formatResult,
267 formatSelection: formatSelection,
267 formatSelection: formatSelection,
268 containerCssClass: "drop-menu status_box_menu",
268 containerCssClass: "drop-menu status_box_menu",
269 dropdownCssClass: "drop-menu-dropdown",
269 dropdownCssClass: "drop-menu-dropdown",
270 dropdownAutoWidth: true,
270 dropdownAutoWidth: true,
271 minimumResultsForSearch: -1
271 minimumResultsForSearch: -1
272 });
272 });
273
273
274 $(this.submitForm).find(this.statusChange).on('change', function() {
274 $(this.submitForm).find(this.statusChange).on('change', function() {
275 var status = self.getCommentStatus();
275 var status = self.getCommentStatus();
276
276
277 if (status && !self.isInline()) {
277 if (status && !self.isInline()) {
278 $(self.submitButton).prop('disabled', false);
278 $(self.submitButton).prop('disabled', false);
279 $(self.submitDraftButton).prop('disabled', false);
279 $(self.submitDraftButton).prop('disabled', false);
280 }
280 }
281
281
282 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
282 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
283 self.setPlaceholder(placeholderText)
283 self.setPlaceholder(placeholderText)
284 })
284 })
285 };
285 };
286
286
287 // reset the comment form into it's original state
287 // reset the comment form into it's original state
288 this.resetCommentFormState = function(content) {
288 this.resetCommentFormState = function(content) {
289 content = content || '';
289 content = content || '';
290
290
291 $(this.editContainer).show();
291 $(this.editContainer).show();
292 $(this.editButton).parent().addClass('active');
292 $(this.editButton).parent().addClass('active');
293
293
294 $(this.previewContainer).hide();
294 $(this.previewContainer).hide();
295 $(this.previewButton).parent().removeClass('active');
295 $(this.previewButton).parent().removeClass('active');
296
296
297 this.setActionButtonsDisabled(true);
297 this.setActionButtonsDisabled(true);
298 self.cm.setValue(content);
298 self.cm.setValue(content);
299 self.cm.setOption("readOnly", false);
299 self.cm.setOption("readOnly", false);
300
300
301 if (this.resolvesId) {
301 if (this.resolvesId) {
302 // destroy the resolve action
302 // destroy the resolve action
303 $(this.resolvesId).parent().remove();
303 $(this.resolvesId).parent().remove();
304 }
304 }
305 // reset closingPR flag
305 // reset closingPR flag
306 $('.close-pr-input').remove();
306 $('.close-pr-input').remove();
307
307
308 $(this.statusChange).select2('readonly', false);
308 $(this.statusChange).select2('readonly', false);
309 };
309 };
310
310
311 this.globalSubmitSuccessCallback = function(comment){
311 this.globalSubmitSuccessCallback = function(comment){
312 // default behaviour is to call GLOBAL hook, if it's registered.
312 // default behaviour is to call GLOBAL hook, if it's registered.
313 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
313 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
314 commentFormGlobalSubmitSuccessCallback(comment);
314 commentFormGlobalSubmitSuccessCallback(comment);
315 }
315 }
316 };
316 };
317
317
318 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
318 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
319 return _submitAjaxPOST(url, postData, successHandler, failHandler);
319 return _submitAjaxPOST(url, postData, successHandler, failHandler);
320 };
320 };
321
321
322 // overwrite a submitHandler, we need to do it for inline comments
322 // overwrite a submitHandler, we need to do it for inline comments
323 this.setHandleFormSubmit = function(callback) {
323 this.setHandleFormSubmit = function(callback) {
324 this.handleFormSubmit = callback;
324 this.handleFormSubmit = callback;
325 };
325 };
326
326
327 // overwrite a submitSuccessHandler
327 // overwrite a submitSuccessHandler
328 this.setGlobalSubmitSuccessCallback = function(callback) {
328 this.setGlobalSubmitSuccessCallback = function(callback) {
329 this.globalSubmitSuccessCallback = callback;
329 this.globalSubmitSuccessCallback = callback;
330 };
330 };
331
331
332 // default handler for for submit for main comments
332 // default handler for for submit for main comments
333 this.handleFormSubmit = function() {
333 this.handleFormSubmit = function() {
334 var text = self.cm.getValue();
334 var text = self.cm.getValue();
335 var status = self.getCommentStatus();
335 var status = self.getCommentStatus();
336 var commentType = self.getCommentType();
336 var commentType = self.getCommentType();
337 var isDraft = self.getDraftState();
337 var isDraft = self.getDraftState();
338 var resolvesCommentId = self.getResolvesId();
338 var resolvesCommentId = self.getResolvesId();
339 var closePullRequest = self.getClosePr();
339 var closePullRequest = self.getClosePr();
340
340
341 if (text === "" && !status) {
341 if (text === "" && !status) {
342 return;
342 return;
343 }
343 }
344
344
345 var excludeCancelBtn = false;
345 var excludeCancelBtn = false;
346 var submitEvent = true;
346 var submitEvent = true;
347 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
347 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
348 self.cm.setOption("readOnly", true);
348 self.cm.setOption("readOnly", true);
349
349
350 var postData = {
350 var postData = {
351 'text': text,
351 'text': text,
352 'changeset_status': status,
352 'changeset_status': status,
353 'comment_type': commentType,
353 'comment_type': commentType,
354 'csrf_token': CSRF_TOKEN
354 'csrf_token': CSRF_TOKEN
355 };
355 };
356
356
357 if (resolvesCommentId) {
357 if (resolvesCommentId) {
358 postData['resolves_comment_id'] = resolvesCommentId;
358 postData['resolves_comment_id'] = resolvesCommentId;
359 }
359 }
360
360
361 if (closePullRequest) {
361 if (closePullRequest) {
362 postData['close_pull_request'] = true;
362 postData['close_pull_request'] = true;
363 }
363 }
364
364
365 // submitSuccess for general comments
365 // submitSuccess for general comments
366 var submitSuccessCallback = function(json_data) {
366 var submitSuccessCallback = function(json_data) {
367 // reload page if we change status for single commit.
367 // reload page if we change status for single commit.
368 if (status && self.commitId) {
368 if (status && self.commitId) {
369 location.reload(true);
369 location.reload(true);
370 } else {
370 } else {
371 // inject newly created comments, json_data is {<comment_id>: {}}
371 // inject newly created comments, json_data is {<comment_id>: {}}
372 Rhodecode.comments.attachGeneralComment(json_data)
372 Rhodecode.comments.attachGeneralComment(json_data)
373
373
374 self.resetCommentFormState();
374 self.resetCommentFormState();
375 timeagoActivate();
375 timeagoActivate();
376 tooltipActivate();
376 tooltipActivate();
377
377
378 // mark visually which comment was resolved
378 // mark visually which comment was resolved
379 if (resolvesCommentId) {
379 if (resolvesCommentId) {
380 self.markCommentResolved(resolvesCommentId);
380 self.markCommentResolved(resolvesCommentId);
381 }
381 }
382 }
382 }
383
383
384 // run global callback on submit
384 // run global callback on submit
385 self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
385 self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
386
386
387 };
387 };
388 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
388 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
389 var prefix = "Error while submitting comment.\n"
389 var prefix = "Error while submitting comment.\n"
390 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
390 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
391 ajaxErrorSwal(message);
391 ajaxErrorSwal(message);
392 self.resetCommentFormState(text);
392 self.resetCommentFormState(text);
393 };
393 };
394 self.submitAjaxPOST(
394 self.submitAjaxPOST(
395 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
395 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
396 };
396 };
397
397
398 this.previewSuccessCallback = function(o) {
398 this.previewSuccessCallback = function(o) {
399 $(self.previewBoxSelector).html(o);
399 $(self.previewBoxSelector).html(o);
400 $(self.previewBoxSelector).removeClass('unloaded');
400 $(self.previewBoxSelector).removeClass('unloaded');
401
401
402 // swap buttons, making preview active
402 // swap buttons, making preview active
403 $(self.previewButton).parent().addClass('active');
403 $(self.previewButton).parent().addClass('active');
404 $(self.editButton).parent().removeClass('active');
404 $(self.editButton).parent().removeClass('active');
405
405
406 // unlock buttons
406 // unlock buttons
407 self.setActionButtonsDisabled(false);
407 self.setActionButtonsDisabled(false);
408 };
408 };
409
409
410 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
410 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
411 excludeCancelBtn = excludeCancelBtn || false;
411 excludeCancelBtn = excludeCancelBtn || false;
412 submitEvent = submitEvent || false;
412 submitEvent = submitEvent || false;
413
413
414 $(this.editButton).prop('disabled', state);
414 $(this.editButton).prop('disabled', state);
415 $(this.previewButton).prop('disabled', state);
415 $(this.previewButton).prop('disabled', state);
416
416
417 if (!excludeCancelBtn) {
417 if (!excludeCancelBtn) {
418 $(this.cancelButton).prop('disabled', state);
418 $(this.cancelButton).prop('disabled', state);
419 }
419 }
420
420
421 var submitState = state;
421 var submitState = state;
422 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
422 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
423 // if the value of commit review status is set, we allow
423 // if the value of commit review status is set, we allow
424 // submit button, but only on Main form, isInline means inline
424 // submit button, but only on Main form, isInline means inline
425 submitState = false
425 submitState = false
426 }
426 }
427
427
428 $(this.submitButton).prop('disabled', submitState);
428 $(this.submitButton).prop('disabled', submitState);
429 $(this.submitDraftButton).prop('disabled', submitState);
429 $(this.submitDraftButton).prop('disabled', submitState);
430
430
431 if (submitEvent) {
431 if (submitEvent) {
432 var isDraft = self.getDraftState();
432 var isDraft = self.getDraftState();
433
433
434 if (isDraft) {
434 if (isDraft) {
435 $(this.submitDraftButton).val(_gettext('Saving Draft...'));
435 $(this.submitDraftButton).val(_gettext('Saving Draft...'));
436 } else {
436 } else {
437 $(this.submitButton).val(_gettext('Submitting...'));
437 $(this.submitButton).val(_gettext('Submitting...'));
438 }
438 }
439
439
440 } else {
440 } else {
441 $(this.submitButton).val(this.submitButtonText);
441 $(this.submitButton).val(this.submitButtonText);
442 $(this.submitDraftButton).val(this.submitDraftButtonText);
442 $(this.submitDraftButton).val(this.submitDraftButtonText);
443 }
443 }
444
444
445 };
445 };
446
446
447 // lock preview/edit/submit buttons on load, but exclude cancel button
447 // lock preview/edit/submit buttons on load, but exclude cancel button
448 var excludeCancelBtn = true;
448 var excludeCancelBtn = true;
449 this.setActionButtonsDisabled(true, excludeCancelBtn);
449 this.setActionButtonsDisabled(true, excludeCancelBtn);
450
450
451 // anonymous users don't have access to initialized CM instance
451 // anonymous users don't have access to initialized CM instance
452 if (this.cm !== undefined){
452 if (this.cm !== undefined){
453 this.cm.on('change', function(cMirror) {
453 this.cm.on('change', function(cMirror) {
454 if (cMirror.getValue() === "") {
454 if (cMirror.getValue() === "") {
455 self.setActionButtonsDisabled(true, excludeCancelBtn)
455 self.setActionButtonsDisabled(true, excludeCancelBtn)
456 } else {
456 } else {
457 self.setActionButtonsDisabled(false, excludeCancelBtn)
457 self.setActionButtonsDisabled(false, excludeCancelBtn)
458 }
458 }
459 });
459 });
460 }
460 }
461
461
462 $(this.editButton).on('click', function(e) {
462 $(this.editButton).on('click', function(e) {
463 e.preventDefault();
463 e.preventDefault();
464
464
465 $(self.previewButton).parent().removeClass('active');
465 $(self.previewButton).parent().removeClass('active');
466 $(self.previewContainer).hide();
466 $(self.previewContainer).hide();
467
467
468 $(self.editButton).parent().addClass('active');
468 $(self.editButton).parent().addClass('active');
469 $(self.editContainer).show();
469 $(self.editContainer).show();
470
470
471 });
471 });
472
472
473 $(this.previewButton).on('click', function(e) {
473 $(this.previewButton).on('click', function(e) {
474 e.preventDefault();
474 e.preventDefault();
475 var text = self.cm.getValue();
475 var text = self.cm.getValue();
476
476
477 if (text === "") {
477 if (text === "") {
478 return;
478 return;
479 }
479 }
480
480
481 var postData = {
481 var postData = {
482 'text': text,
482 'text': text,
483 'renderer': templateContext.visual.default_renderer,
483 'renderer': templateContext.visual.default_renderer,
484 'csrf_token': CSRF_TOKEN
484 'csrf_token': CSRF_TOKEN
485 };
485 };
486
486
487 // lock ALL buttons on preview
487 // lock ALL buttons on preview
488 self.setActionButtonsDisabled(true);
488 self.setActionButtonsDisabled(true);
489
489
490 $(self.previewBoxSelector).addClass('unloaded');
490 $(self.previewBoxSelector).addClass('unloaded');
491 $(self.previewBoxSelector).html(_gettext('Loading ...'));
491 $(self.previewBoxSelector).html(_gettext('Loading ...'));
492
492
493 $(self.editContainer).hide();
493 $(self.editContainer).hide();
494 $(self.previewContainer).show();
494 $(self.previewContainer).show();
495
495
496 // by default we reset state of comment preserving the text
496 // by default we reset state of comment preserving the text
497 var previewFailCallback = function(jqXHR, textStatus, errorThrown) {
497 var previewFailCallback = function(jqXHR, textStatus, errorThrown) {
498 var prefix = "Error while preview of comment.\n"
498 var prefix = "Error while preview of comment.\n"
499 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
499 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
500 ajaxErrorSwal(message);
500 ajaxErrorSwal(message);
501
501
502 self.resetCommentFormState(text)
502 self.resetCommentFormState(text)
503 };
503 };
504 self.submitAjaxPOST(
504 self.submitAjaxPOST(
505 self.previewUrl, postData, self.previewSuccessCallback,
505 self.previewUrl, postData, self.previewSuccessCallback,
506 previewFailCallback);
506 previewFailCallback);
507
507
508 $(self.previewButton).parent().addClass('active');
508 $(self.previewButton).parent().addClass('active');
509 $(self.editButton).parent().removeClass('active');
509 $(self.editButton).parent().removeClass('active');
510 });
510 });
511
511
512 $(this.submitForm).submit(function(e) {
512 $(this.submitForm).submit(function(e) {
513 e.preventDefault();
513 e.preventDefault();
514 var allowedToSubmit = self.isAllowedToSubmit();
514 var allowedToSubmit = self.isAllowedToSubmit();
515 if (!allowedToSubmit){
515 if (!allowedToSubmit){
516 return false;
516 return false;
517 }
517 }
518
518
519 self.handleFormSubmit();
519 self.handleFormSubmit();
520 });
520 });
521
521
522 }
522 }
523
523
524 return CommentForm;
524 return CommentForm;
525 });
525 });
526
526
527 /* selector for comment versions */
527 /* selector for comment versions */
528 var initVersionSelector = function(selector, initialData) {
528 var initVersionSelector = function(selector, initialData) {
529
529
530 var formatResult = function(result, container, query, escapeMarkup) {
530 var formatResult = function(result, container, query, escapeMarkup) {
531
531
532 return renderTemplate('commentVersion', {
532 return renderTemplate('commentVersion', {
533 show_disabled: true,
533 show_disabled: true,
534 version: result.comment_version,
534 version: result.comment_version,
535 user_name: result.comment_author_username,
535 user_name: result.comment_author_username,
536 gravatar_url: result.comment_author_gravatar,
536 gravatar_url: result.comment_author_gravatar,
537 size: 16,
537 size: 16,
538 timeago_component: result.comment_created_on,
538 timeago_component: result.comment_created_on,
539 })
539 })
540 };
540 };
541
541
542 $(selector).select2({
542 $(selector).select2({
543 placeholder: "Edited",
543 placeholder: "Edited",
544 containerCssClass: "drop-menu-comment-history",
544 containerCssClass: "drop-menu-comment-history",
545 dropdownCssClass: "drop-menu-dropdown",
545 dropdownCssClass: "drop-menu-dropdown",
546 dropdownAutoWidth: true,
546 dropdownAutoWidth: true,
547 minimumResultsForSearch: -1,
547 minimumResultsForSearch: -1,
548 data: initialData,
548 data: initialData,
549 formatResult: formatResult,
549 formatResult: formatResult,
550 });
550 });
551
551
552 $(selector).on('select2-selecting', function (e) {
552 $(selector).on('select2-selecting', function (e) {
553 // hide the mast as we later do preventDefault()
553 // hide the mast as we later do preventDefault()
554 $("#select2-drop-mask").click();
554 $("#select2-drop-mask").click();
555 e.preventDefault();
555 e.preventDefault();
556 e.choice.action();
556 e.choice.action();
557 });
557 });
558
558
559 $(selector).on("select2-open", function() {
559 $(selector).on("select2-open", function() {
560 timeagoActivate();
560 timeagoActivate();
561 });
561 });
562 };
562 };
563
563
564 /* comments controller */
564 /* comments controller */
565 var CommentsController = function() {
565 var CommentsController = function() {
566 var mainComment = '#text';
566 var mainComment = '#text';
567 var self = this;
567 var self = this;
568
568
569 this.showVersion = function (comment_id, comment_history_id) {
569 this.showVersion = function (comment_id, comment_history_id) {
570
570
571 var historyViewUrl = pyroutes.url(
571 var historyViewUrl = pyroutes.url(
572 'repo_commit_comment_history_view',
572 'repo_commit_comment_history_view',
573 {
573 {
574 'repo_name': templateContext.repo_name,
574 'repo_name': templateContext.repo_name,
575 'commit_id': comment_id,
575 'commit_id': comment_id,
576 'comment_history_id': comment_history_id,
576 'comment_history_id': comment_history_id,
577 }
577 }
578 );
578 );
579 successRenderCommit = function (data) {
579 successRenderCommit = function (data) {
580 SwalNoAnimation.fire({
580 SwalNoAnimation.fire({
581 html: data,
581 html: data,
582 title: '',
582 title: '',
583 });
583 });
584 };
584 };
585 failRenderCommit = function () {
585 failRenderCommit = function () {
586 SwalNoAnimation.fire({
586 SwalNoAnimation.fire({
587 html: 'Error while loading comment history',
587 html: 'Error while loading comment history',
588 title: '',
588 title: '',
589 });
589 });
590 };
590 };
591 _submitAjaxPOST(
591 _submitAjaxPOST(
592 historyViewUrl, {'csrf_token': CSRF_TOKEN},
592 historyViewUrl, {'csrf_token': CSRF_TOKEN},
593 successRenderCommit,
593 successRenderCommit,
594 failRenderCommit
594 failRenderCommit
595 );
595 );
596 };
596 };
597
597
598 this.getLineNumber = function(node) {
598 this.getLineNumber = function(node) {
599 var $node = $(node);
599 var $node = $(node);
600 var lineNo = $node.closest('td').attr('data-line-no');
600 var lineNo = $node.closest('td').attr('data-line-no');
601 if (lineNo === undefined && $node.data('commentInline')){
601 if (lineNo === undefined && $node.data('commentInline')){
602 lineNo = $node.data('commentLineNo')
602 lineNo = $node.data('commentLineNo')
603 }
603 }
604
604
605 return lineNo
605 return lineNo
606 };
606 };
607
607
608 this.scrollToComment = function(node, offset, outdated) {
608 this.scrollToComment = function(node, offset, outdated) {
609 if (offset === undefined) {
609 if (offset === undefined) {
610 offset = 0;
610 offset = 0;
611 }
611 }
612 var outdated = outdated || false;
612 var outdated = outdated || false;
613 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
613 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
614
614
615 if (!node) {
615 if (!node) {
616 node = $('.comment-selected');
616 node = $('.comment-selected');
617 if (!node.length) {
617 if (!node.length) {
618 node = $('comment-current')
618 node = $('comment-current')
619 }
619 }
620 }
620 }
621
621
622 $wrapper = $(node).closest('div.comment');
622 $wrapper = $(node).closest('div.comment');
623
623
624 // show hidden comment when referenced.
624 // show hidden comment when referenced.
625 if (!$wrapper.is(':visible')){
625 if (!$wrapper.is(':visible')){
626 $wrapper.show();
626 $wrapper.show();
627 }
627 }
628
628
629 $comment = $(node).closest(klass);
629 $comment = $(node).closest(klass);
630 $comments = $(klass);
630 $comments = $(klass);
631
631
632 $('.comment-selected').removeClass('comment-selected');
632 $('.comment-selected').removeClass('comment-selected');
633
633
634 var nextIdx = $(klass).index($comment) + offset;
634 var nextIdx = $(klass).index($comment) + offset;
635 if (nextIdx >= $comments.length) {
635 if (nextIdx >= $comments.length) {
636 nextIdx = 0;
636 nextIdx = 0;
637 }
637 }
638 var $next = $(klass).eq(nextIdx);
638 var $next = $(klass).eq(nextIdx);
639
639
640 var $cb = $next.closest('.cb');
640 var $cb = $next.closest('.cb');
641 $cb.removeClass('cb-collapsed');
641 $cb.removeClass('cb-collapsed');
642
642
643 var $filediffCollapseState = $cb.closest('.filediff').prev();
643 var $filediffCollapseState = $cb.closest('.filediff').prev();
644 $filediffCollapseState.prop('checked', false);
644 $filediffCollapseState.prop('checked', false);
645 $next.addClass('comment-selected');
645 $next.addClass('comment-selected');
646 scrollToElement($next);
646 scrollToElement($next);
647 return false;
647 return false;
648 };
648 };
649
649
650 this.nextComment = function(node) {
650 this.nextComment = function(node) {
651 return self.scrollToComment(node, 1);
651 return self.scrollToComment(node, 1);
652 };
652 };
653
653
654 this.prevComment = function(node) {
654 this.prevComment = function(node) {
655 return self.scrollToComment(node, -1);
655 return self.scrollToComment(node, -1);
656 };
656 };
657
657
658 this.nextOutdatedComment = function(node) {
658 this.nextOutdatedComment = function(node) {
659 return self.scrollToComment(node, 1, true);
659 return self.scrollToComment(node, 1, true);
660 };
660 };
661
661
662 this.prevOutdatedComment = function(node) {
662 this.prevOutdatedComment = function(node) {
663 return self.scrollToComment(node, -1, true);
663 return self.scrollToComment(node, -1, true);
664 };
664 };
665
665
666 this.cancelComment = function (node) {
666 this.cancelComment = function (node) {
667 var $node = $(node);
667 var $node = $(node);
668 var edit = $(this).attr('edit');
668 var edit = $(this).attr('edit');
669 var $inlineComments = $node.closest('div.inline-comments');
669 var $inlineComments = $node.closest('div.inline-comments');
670
670
671 if (edit) {
671 if (edit) {
672 var $general_comments = null;
672 var $general_comments = null;
673 if (!$inlineComments.length) {
673 if (!$inlineComments.length) {
674 $general_comments = $('#comments');
674 $general_comments = $('#comments');
675 var $comment = $general_comments.parent().find('div.comment:hidden');
675 var $comment = $general_comments.parent().find('div.comment:hidden');
676 // show hidden general comment form
676 // show hidden general comment form
677 $('#cb-comment-general-form-placeholder').show();
677 $('#cb-comment-general-form-placeholder').show();
678 } else {
678 } else {
679 var $comment = $inlineComments.find('div.comment:hidden');
679 var $comment = $inlineComments.find('div.comment:hidden');
680 }
680 }
681 $comment.show();
681 $comment.show();
682 }
682 }
683 var $replyWrapper = $node.closest('.comment-inline-form').closest('.reply-thread-container-wrapper')
683 var $replyWrapper = $node.closest('.comment-inline-form').closest('.reply-thread-container-wrapper')
684 $replyWrapper.removeClass('comment-form-active');
684 $replyWrapper.removeClass('comment-form-active');
685
685
686 var lastComment = $inlineComments.find('.comment-inline').last();
686 var lastComment = $inlineComments.find('.comment-inline').last();
687 if ($(lastComment).hasClass('comment-outdated')) {
687 if ($(lastComment).hasClass('comment-outdated')) {
688 $replyWrapper.hide();
688 $replyWrapper.hide();
689 }
689 }
690
690
691 $node.closest('.comment-inline-form').remove();
691 $node.closest('.comment-inline-form').remove();
692 return false;
692 return false;
693 };
693 };
694
694
695 this._deleteComment = function(node) {
695 this._deleteComment = function(node) {
696 var $node = $(node);
696 var $node = $(node);
697 var $td = $node.closest('td');
697 var $td = $node.closest('td');
698 var $comment = $node.closest('.comment');
698 var $comment = $node.closest('.comment');
699 var comment_id = $($comment).data('commentId');
699 var comment_id = $($comment).data('commentId');
700 var isDraft = $($comment).data('commentDraft');
700 var isDraft = $($comment).data('commentDraft');
701
701
702 var pullRequestId = templateContext.pull_request_data.pull_request_id;
702 var pullRequestId = templateContext.pull_request_data.pull_request_id;
703 var commitId = templateContext.commit_data.commit_id;
703 var commitId = templateContext.commit_data.commit_id;
704
704
705 if (pullRequestId) {
705 if (pullRequestId) {
706 var url = pyroutes.url('pullrequest_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "pull_request_id": pullRequestId})
706 var url = pyroutes.url('pullrequest_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "pull_request_id": pullRequestId})
707 } else if (commitId) {
707 } else if (commitId) {
708 var url = pyroutes.url('repo_commit_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "commit_id": commitId})
708 var url = pyroutes.url('repo_commit_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "commit_id": commitId})
709 }
709 }
710
710
711 var postData = {
711 var postData = {
712 'csrf_token': CSRF_TOKEN
712 'csrf_token': CSRF_TOKEN
713 };
713 };
714
714
715 $comment.addClass('comment-deleting');
715 $comment.addClass('comment-deleting');
716 $comment.hide('fast');
716 $comment.hide('fast');
717
717
718 var success = function(response) {
718 var success = function(response) {
719 $comment.remove();
719 $comment.remove();
720
720
721 if (window.updateSticky !== undefined) {
721 if (window.updateSticky !== undefined) {
722 // potentially our comments change the active window size, so we
722 // potentially our comments change the active window size, so we
723 // notify sticky elements
723 // notify sticky elements
724 updateSticky()
724 updateSticky()
725 }
725 }
726
726
727 if (window.refreshAllComments !== undefined && !isDraft) {
727 if (window.refreshAllComments !== undefined && !isDraft) {
728 // if we have this handler, run it, and refresh all comments boxes
728 // if we have this handler, run it, and refresh all comments boxes
729 refreshAllComments()
729 refreshAllComments()
730 }
730 }
731 else if (window.refreshDraftComments !== undefined && isDraft) {
731 else if (window.refreshDraftComments !== undefined && isDraft) {
732 // if we have this handler, run it, and refresh all comments boxes
732 // if we have this handler, run it, and refresh all comments boxes
733 refreshDraftComments();
733 refreshDraftComments();
734 }
734 }
735 return false;
735 return false;
736 };
736 };
737
737
738 var failure = function(jqXHR, textStatus, errorThrown) {
738 var failure = function(jqXHR, textStatus, errorThrown) {
739 var prefix = "Error while deleting this comment.\n"
739 var prefix = "Error while deleting this comment.\n"
740 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
740 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
741 ajaxErrorSwal(message);
741 ajaxErrorSwal(message);
742
742
743 $comment.show('fast');
743 $comment.show('fast');
744 $comment.removeClass('comment-deleting');
744 $comment.removeClass('comment-deleting');
745 return false;
745 return false;
746 };
746 };
747 ajaxPOST(url, postData, success, failure);
747 ajaxPOST(url, postData, success, failure);
748
748
749 }
749 }
750
750
751 this.deleteComment = function(node) {
751 this.deleteComment = function(node) {
752 var $comment = $(node).closest('.comment');
752 var $comment = $(node).closest('.comment');
753 var comment_id = $comment.attr('data-comment-id');
753 var comment_id = $comment.attr('data-comment-id');
754
754
755 SwalNoAnimation.fire({
755 SwalNoAnimation.fire({
756 title: 'Delete this comment?',
756 title: 'Delete this comment?',
757 icon: 'warning',
757 icon: 'warning',
758 showCancelButton: true,
758 showCancelButton: true,
759 confirmButtonText: _gettext('Yes, delete comment #{0}!').format(comment_id),
759 confirmButtonText: _gettext('Yes, delete comment #{0}!').format(comment_id),
760
760
761 }).then(function(result) {
761 }).then(function(result) {
762 if (result.value) {
762 if (result.value) {
763 self._deleteComment(node);
763 self._deleteComment(node);
764 }
764 }
765 })
765 })
766 };
766 };
767
767
768 this._finalizeDrafts = function(commentIds) {
768 this._finalizeDrafts = function(commentIds) {
769
769
770 var pullRequestId = templateContext.pull_request_data.pull_request_id;
770 var pullRequestId = templateContext.pull_request_data.pull_request_id;
771 var commitId = templateContext.commit_data.commit_id;
771 var commitId = templateContext.commit_data.commit_id;
772
772
773 if (pullRequestId) {
773 if (pullRequestId) {
774 var url = pyroutes.url('pullrequest_draft_comments_submit', {"repo_name": templateContext.repo_name, "pull_request_id": pullRequestId})
774 var url = pyroutes.url('pullrequest_draft_comments_submit', {"repo_name": templateContext.repo_name, "pull_request_id": pullRequestId})
775 } else if (commitId) {
775 } else if (commitId) {
776 var url = pyroutes.url('commit_draft_comments_submit', {"repo_name": templateContext.repo_name, "commit_id": commitId})
776 var url = pyroutes.url('commit_draft_comments_submit', {"repo_name": templateContext.repo_name, "commit_id": commitId})
777 }
777 }
778
778
779 // remove the drafts so we can lock them before submit.
779 // remove the drafts so we can lock them before submit.
780 $.each(commentIds, function(idx, val){
780 $.each(commentIds, function(idx, val){
781 $('#comment-{0}'.format(val)).remove();
781 $('#comment-{0}'.format(val)).remove();
782 })
782 })
783
783
784 var postData = {'comments': commentIds, 'csrf_token': CSRF_TOKEN};
784 var postData = {'comments': commentIds, 'csrf_token': CSRF_TOKEN};
785
785
786 var submitSuccessCallback = function(json_data) {
786 var submitSuccessCallback = function(json_data) {
787 self.attachInlineComment(json_data);
787 self.attachInlineComment(json_data);
788
788
789 if (window.refreshDraftComments !== undefined) {
789 if (window.refreshDraftComments !== undefined) {
790 // if we have this handler, run it, and refresh all comments boxes
790 // if we have this handler, run it, and refresh all comments boxes
791 refreshDraftComments()
791 refreshDraftComments()
792 }
792 }
793
793
794 return false;
794 return false;
795 };
795 };
796
796
797 ajaxPOST(url, postData, submitSuccessCallback)
797 ajaxPOST(url, postData, submitSuccessCallback)
798
798
799 }
799 }
800
800
801 this.finalizeDrafts = function(commentIds, callback) {
801 this.finalizeDrafts = function(commentIds, callback) {
802
802
803 SwalNoAnimation.fire({
803 SwalNoAnimation.fire({
804 title: _ngettext('Submit {0} draft comment.', 'Submit {0} draft comments.', commentIds.length).format(commentIds.length),
804 title: _ngettext('Submit {0} draft comment.', 'Submit {0} draft comments.', commentIds.length).format(commentIds.length),
805 icon: 'warning',
805 icon: 'warning',
806 showCancelButton: true,
806 showCancelButton: true,
807 confirmButtonText: _gettext('Yes'),
807 confirmButtonText: _gettext('Yes'),
808
808
809 }).then(function(result) {
809 }).then(function(result) {
810 if (result.value) {
810 if (result.value) {
811 if (callback !== undefined) {
811 if (callback !== undefined) {
812 callback(result)
812 callback(result)
813 }
813 }
814 self._finalizeDrafts(commentIds);
814 self._finalizeDrafts(commentIds);
815 }
815 }
816 })
816 })
817 };
817 };
818
818
819 this.toggleWideMode = function (node) {
819 this.toggleWideMode = function (node) {
820
820
821 if ($('#content').hasClass('wrapper')) {
821 if ($('#content').hasClass('wrapper')) {
822 $('#content').removeClass("wrapper");
822 $('#content').removeClass("wrapper");
823 $('#content').addClass("wide-mode-wrapper");
823 $('#content').addClass("wide-mode-wrapper");
824 $(node).addClass('btn-success');
824 $(node).addClass('btn-success');
825 return true
825 return true
826 } else {
826 } else {
827 $('#content').removeClass("wide-mode-wrapper");
827 $('#content').removeClass("wide-mode-wrapper");
828 $('#content').addClass("wrapper");
828 $('#content').addClass("wrapper");
829 $(node).removeClass('btn-success');
829 $(node).removeClass('btn-success');
830 return false
830 return false
831 }
831 }
832
832
833 };
833 };
834
834
835 /**
835 /**
836 * Turn off/on all comments in file diff
836 * Turn off/on all comments in file diff
837 */
837 */
838 this.toggleDiffComments = function(node) {
838 this.toggleDiffComments = function(node) {
839 // Find closes filediff container
839 // Find closes filediff container
840 var $filediff = $(node).closest('.filediff');
840 var $filediff = $(node).closest('.filediff');
841 if ($(node).hasClass('toggle-on')) {
841 if ($(node).hasClass('toggle-on')) {
842 var show = false;
842 var show = false;
843 } else if ($(node).hasClass('toggle-off')) {
843 } else if ($(node).hasClass('toggle-off')) {
844 var show = true;
844 var show = true;
845 }
845 }
846
846
847 // Toggle each individual comment block, so we can un-toggle single ones
847 // Toggle each individual comment block, so we can un-toggle single ones
848 $.each($filediff.find('.toggle-comment-action'), function(idx, val) {
848 $.each($filediff.find('.toggle-comment-action'), function(idx, val) {
849 self.toggleLineComments($(val), show)
849 self.toggleLineComments($(val), show)
850 })
850 })
851
851
852 // since we change the height of the diff container that has anchor points for upper
852 // since we change the height of the diff container that has anchor points for upper
853 // sticky header, we need to tell it to re-calculate those
853 // sticky header, we need to tell it to re-calculate those
854 if (window.updateSticky !== undefined) {
854 if (window.updateSticky !== undefined) {
855 // potentially our comments change the active window size, so we
855 // potentially our comments change the active window size, so we
856 // notify sticky elements
856 // notify sticky elements
857 updateSticky()
857 updateSticky()
858 }
858 }
859
859
860 return false;
860 return false;
861 }
861 }
862
862
863 this.toggleLineComments = function(node, show) {
863 this.toggleLineComments = function(node, show) {
864
864
865 var trElem = $(node).closest('tr')
865 var trElem = $(node).closest('tr')
866
866
867 if (show === true) {
867 if (show === true) {
868 // mark outdated comments as visible before the toggle;
868 // mark outdated comments as visible before the toggle;
869 $(trElem).find('.comment-outdated').show();
869 $(trElem).find('.comment-outdated').show();
870 $(trElem).removeClass('hide-line-comments');
870 $(trElem).removeClass('hide-line-comments');
871 } else if (show === false) {
871 } else if (show === false) {
872 $(trElem).find('.comment-outdated').hide();
872 $(trElem).find('.comment-outdated').hide();
873 $(trElem).addClass('hide-line-comments');
873 $(trElem).addClass('hide-line-comments');
874 } else {
874 } else {
875 // mark outdated comments as visible before the toggle;
875 // mark outdated comments as visible before the toggle;
876 $(trElem).find('.comment-outdated').show();
876 $(trElem).find('.comment-outdated').show();
877 $(trElem).toggleClass('hide-line-comments');
877 $(trElem).toggleClass('hide-line-comments');
878 }
878 }
879
879
880 // since we change the height of the diff container that has anchor points for upper
880 // since we change the height of the diff container that has anchor points for upper
881 // sticky header, we need to tell it to re-calculate those
881 // sticky header, we need to tell it to re-calculate those
882 if (window.updateSticky !== undefined) {
882 if (window.updateSticky !== undefined) {
883 // potentially our comments change the active window size, so we
883 // potentially our comments change the active window size, so we
884 // notify sticky elements
884 // notify sticky elements
885 updateSticky()
885 updateSticky()
886 }
886 }
887
887
888 };
888 };
889
889
890 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
890 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
891 var pullRequestId = templateContext.pull_request_data.pull_request_id;
891 var pullRequestId = templateContext.pull_request_data.pull_request_id;
892 var commitId = templateContext.commit_data.commit_id;
892 var commitId = templateContext.commit_data.commit_id;
893
893
894 var commentForm = new CommentForm(
894 var commentForm = new CommentForm(
895 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId, edit, comment_id);
895 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId, edit, comment_id);
896 var cm = commentForm.getCmInstance();
896 var cm = commentForm.getCmInstance();
897
897
898 if (resolvesCommentId){
898 if (resolvesCommentId){
899 placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
899 placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
900 }
900 }
901
901
902 setTimeout(function() {
902 setTimeout(function() {
903 // callbacks
903 // callbacks
904 if (cm !== undefined) {
904 if (cm !== undefined) {
905 commentForm.setPlaceholder(placeholderText);
905 commentForm.setPlaceholder(placeholderText);
906 if (commentForm.isInline()) {
906 if (commentForm.isInline()) {
907 cm.focus();
907 cm.focus();
908 cm.refresh();
908 cm.refresh();
909 }
909 }
910 }
910 }
911 }, 10);
911 }, 10);
912
912
913 // trigger scrolldown to the resolve comment, since it might be away
913 // trigger scrolldown to the resolve comment, since it might be away
914 // from the clicked
914 // from the clicked
915 if (resolvesCommentId){
915 if (resolvesCommentId){
916 var actionNode = $(commentForm.resolvesActionId).offset();
916 var actionNode = $(commentForm.resolvesActionId).offset();
917
917
918 setTimeout(function() {
918 setTimeout(function() {
919 if (actionNode) {
919 if (actionNode) {
920 $('body, html').animate({scrollTop: actionNode.top}, 10);
920 $('body, html').animate({scrollTop: actionNode.top}, 10);
921 }
921 }
922 }, 100);
922 }, 100);
923 }
923 }
924
924
925 // add dropzone support
925 // add dropzone support
926 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
926 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
927 var renderer = templateContext.visual.default_renderer;
927 var renderer = templateContext.visual.default_renderer;
928 if (renderer == 'rst') {
928 if (renderer == 'rst') {
929 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
929 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
930 if (isRendered){
930 if (isRendered){
931 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
931 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
932 }
932 }
933 } else if (renderer == 'markdown') {
933 } else if (renderer == 'markdown') {
934 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
934 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
935 if (isRendered){
935 if (isRendered){
936 attachmentUrl = '!' + attachmentUrl;
936 attachmentUrl = '!' + attachmentUrl;
937 }
937 }
938 } else {
938 } else {
939 var attachmentUrl = '{}'.format(attachmentStoreUrl);
939 var attachmentUrl = '{}'.format(attachmentStoreUrl);
940 }
940 }
941 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
941 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
942
942
943 return false;
943 return false;
944 };
944 };
945
945
946 //see: https://www.dropzonejs.com/#configuration
946 //see: https://www.dropzonejs.com/#configuration
947 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
947 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
948 {'repo_name': templateContext.repo_name,
948 {'repo_name': templateContext.repo_name,
949 'commit_id': templateContext.commit_data.commit_id})
949 'commit_id': templateContext.commit_data.commit_id})
950
950
951 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
951 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
952 if (previewTmpl !== undefined){
952 if (previewTmpl !== undefined){
953 var selectLink = $(formElement).find('.pick-attachment').get(0);
953 var selectLink = $(formElement).find('.pick-attachment').get(0);
954 $(formElement).find('.comment-attachment-uploader').dropzone({
954 $(formElement).find('.comment-attachment-uploader').dropzone({
955 url: storeUrl,
955 url: storeUrl,
956 headers: {"X-CSRF-Token": CSRF_TOKEN},
956 headers: {"X-CSRF-Token": CSRF_TOKEN},
957 paramName: function () {
957 paramName: function () {
958 return "attachment"
958 return "attachment"
959 }, // The name that will be used to transfer the file
959 }, // The name that will be used to transfer the file
960 clickable: selectLink,
960 clickable: selectLink,
961 parallelUploads: 1,
961 parallelUploads: 1,
962 maxFiles: 10,
962 maxFiles: 10,
963 maxFilesize: templateContext.attachment_store.max_file_size_mb,
963 maxFilesize: templateContext.attachment_store.max_file_size_mb,
964 uploadMultiple: false,
964 uploadMultiple: false,
965 autoProcessQueue: true, // if false queue will not be processed automatically.
965 autoProcessQueue: true, // if false queue will not be processed automatically.
966 createImageThumbnails: false,
966 createImageThumbnails: false,
967 previewTemplate: previewTmpl.innerHTML,
967 previewTemplate: previewTmpl.innerHTML,
968
968
969 accept: function (file, done) {
969 accept: function (file, done) {
970 done();
970 done();
971 },
971 },
972 init: function () {
972 init: function () {
973
973
974 this.on("sending", function (file, xhr, formData) {
974 this.on("sending", function (file, xhr, formData) {
975 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
975 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
976 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
976 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
977 });
977 });
978
978
979 this.on("success", function (file, response) {
979 this.on("success", function (file, response) {
980 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
980 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
981 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
981 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
982
982
983 var isRendered = false;
983 var isRendered = false;
984 var ext = file.name.split('.').pop();
984 var ext = file.name.split('.').pop();
985 var imageExts = templateContext.attachment_store.image_ext;
985 var imageExts = templateContext.attachment_store.image_ext;
986 if (imageExts.indexOf(ext) !== -1){
986 if (imageExts.indexOf(ext) !== -1){
987 isRendered = true;
987 isRendered = true;
988 }
988 }
989
989
990 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
990 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
991 });
991 });
992
992
993 this.on("error", function (file, errorMessage, xhr) {
993 this.on("error", function (file, errorMessage, xhr) {
994 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
994 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
995
995
996 var error = null;
996 var error = null;
997
997
998 if (xhr !== undefined){
998 if (xhr !== undefined){
999 var httpStatus = xhr.status + " " + xhr.statusText;
999 var httpStatus = xhr.status + " " + xhr.statusText;
1000 if (xhr !== undefined && xhr.status >= 500) {
1000 if (xhr !== undefined && xhr.status >= 500) {
1001 error = httpStatus;
1001 error = httpStatus;
1002 }
1002 }
1003 }
1003 }
1004
1004
1005 if (error === null) {
1005 if (error === null) {
1006 error = errorMessage.error || errorMessage || httpStatus;
1006 error = errorMessage.error || errorMessage || httpStatus;
1007 }
1007 }
1008 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
1008 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
1009
1009
1010 });
1010 });
1011 }
1011 }
1012 });
1012 });
1013 }
1013 }
1014 return commentForm;
1014 return commentForm;
1015 };
1015 };
1016
1016
1017 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
1017 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
1018
1018
1019 var tmpl = $('#cb-comment-general-form-template').html();
1019 var tmpl = $('#cb-comment-general-form-template').html();
1020 tmpl = tmpl.format(null, 'general');
1020 tmpl = tmpl.format(null, 'general');
1021 var $form = $(tmpl);
1021 var $form = $(tmpl);
1022
1022
1023 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
1023 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
1024 var curForm = $formPlaceholder.find('form');
1024 var curForm = $formPlaceholder.find('form');
1025 if (curForm){
1025 if (curForm){
1026 curForm.remove();
1026 curForm.remove();
1027 }
1027 }
1028 $formPlaceholder.append($form);
1028 $formPlaceholder.append($form);
1029
1029
1030 var _form = $($form[0]);
1030 var _form = $($form[0]);
1031 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
1031 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
1032 var edit = false;
1032 var edit = false;
1033 var comment_id = null;
1033 var comment_id = null;
1034 var commentForm = this.createCommentForm(
1034 var commentForm = this.createCommentForm(
1035 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId, edit, comment_id);
1035 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId, edit, comment_id);
1036 commentForm.initStatusChangeSelector();
1036 commentForm.initStatusChangeSelector();
1037
1037
1038 return commentForm;
1038 return commentForm;
1039 };
1039 };
1040
1040
1041 this.editComment = function(node, line_no, f_path) {
1041 this.editComment = function(node, line_no, f_path) {
1042 self.edit = true;
1042 self.edit = true;
1043 var $node = $(node);
1043 var $node = $(node);
1044 var $td = $node.closest('td');
1044 var $td = $node.closest('td');
1045
1045
1046 var $comment = $(node).closest('.comment');
1046 var $comment = $(node).closest('.comment');
1047 var comment_id = $($comment).data('commentId');
1047 var comment_id = $($comment).data('commentId');
1048 var isDraft = $($comment).data('commentDraft');
1048 var isDraft = $($comment).data('commentDraft');
1049 var $editForm = null
1049 var $editForm = null
1050
1050
1051 var $comments = $node.closest('div.inline-comments');
1051 var $comments = $node.closest('div.inline-comments');
1052 var $general_comments = null;
1052 var $general_comments = null;
1053
1053
1054 if($comments.length){
1054 if($comments.length){
1055 // inline comments setup
1055 // inline comments setup
1056 $editForm = $comments.find('.comment-inline-form');
1056 $editForm = $comments.find('.comment-inline-form');
1057 line_no = self.getLineNumber(node)
1057 line_no = self.getLineNumber(node)
1058 }
1058 }
1059 else{
1059 else{
1060 // general comments setup
1060 // general comments setup
1061 $comments = $('#comments');
1061 $comments = $('#comments');
1062 $editForm = $comments.find('.comment-inline-form');
1062 $editForm = $comments.find('.comment-inline-form');
1063 line_no = $comment[0].id
1063 line_no = $comment[0].id
1064 $('#cb-comment-general-form-placeholder').hide();
1064 $('#cb-comment-general-form-placeholder').hide();
1065 }
1065 }
1066
1066
1067 if ($editForm.length === 0) {
1067 if ($editForm.length === 0) {
1068
1068
1069 // unhide all comments if they are hidden for a proper REPLY mode
1069 // unhide all comments if they are hidden for a proper REPLY mode
1070 var $filediff = $node.closest('.filediff');
1070 var $filediff = $node.closest('.filediff');
1071 $filediff.removeClass('hide-comments');
1071 $filediff.removeClass('hide-comments');
1072
1072
1073 $editForm = self.createNewFormWrapper(f_path, line_no);
1073 $editForm = self.createNewFormWrapper(f_path, line_no);
1074 if(f_path && line_no) {
1074 if(f_path && line_no) {
1075 $editForm.addClass('comment-inline-form-edit')
1075 $editForm.addClass('comment-inline-form-edit')
1076 }
1076 }
1077
1077
1078 $comment.after($editForm)
1078 $comment.after($editForm)
1079
1079
1080 var _form = $($editForm[0]).find('form');
1080 var _form = $($editForm[0]).find('form');
1081 var autocompleteActions = ['as_note',];
1081 var autocompleteActions = ['as_note',];
1082 var commentForm = this.createCommentForm(
1082 var commentForm = this.createCommentForm(
1083 _form, line_no, '', autocompleteActions, resolvesCommentId,
1083 _form, line_no, '', autocompleteActions, resolvesCommentId,
1084 this.edit, comment_id);
1084 this.edit, comment_id);
1085 var old_comment_text_binary = $comment.attr('data-comment-text');
1085 var old_comment_text_binary = $comment.attr('data-comment-text');
1086 var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
1086 var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
1087 commentForm.cm.setValue(old_comment_text);
1087 commentForm.cm.setValue(old_comment_text);
1088 $comment.hide();
1088 $comment.hide();
1089 tooltipActivate();
1089 tooltipActivate();
1090
1090
1091 // set a CUSTOM submit handler for inline comment edit action.
1091 // set a CUSTOM submit handler for inline comment edit action.
1092 commentForm.setHandleFormSubmit(function(o) {
1092 commentForm.setHandleFormSubmit(function(o) {
1093 var text = commentForm.cm.getValue();
1093 var text = commentForm.cm.getValue();
1094 var commentType = commentForm.getCommentType();
1094 var commentType = commentForm.getCommentType();
1095
1095
1096 if (text === "") {
1096 if (text === "") {
1097 return;
1097 return;
1098 }
1098 }
1099
1099
1100 if (old_comment_text == text) {
1100 if (old_comment_text == text) {
1101 SwalNoAnimation.fire({
1101 SwalNoAnimation.fire({
1102 title: 'Unable to edit comment',
1102 title: 'Unable to edit comment',
1103 html: _gettext('Comment body was not changed.'),
1103 html: _gettext('Comment body was not changed.'),
1104 });
1104 });
1105 return;
1105 return;
1106 }
1106 }
1107 var excludeCancelBtn = false;
1107 var excludeCancelBtn = false;
1108 var submitEvent = true;
1108 var submitEvent = true;
1109 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1109 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1110 commentForm.cm.setOption("readOnly", true);
1110 commentForm.cm.setOption("readOnly", true);
1111
1111
1112 // Read last version known
1112 // Read last version known
1113 var versionSelector = $('#comment_versions_{0}'.format(comment_id));
1113 var versionSelector = $('#comment_versions_{0}'.format(comment_id));
1114 var version = versionSelector.data('lastVersion');
1114 var version = versionSelector.data('lastVersion');
1115
1115
1116 if (!version) {
1116 if (!version) {
1117 version = 0;
1117 version = 0;
1118 }
1118 }
1119
1119
1120 var postData = {
1120 var postData = {
1121 'text': text,
1121 'text': text,
1122 'f_path': f_path,
1122 'f_path': f_path,
1123 'line': line_no,
1123 'line': line_no,
1124 'comment_type': commentType,
1124 'comment_type': commentType,
1125 'draft': isDraft,
1125 'draft': isDraft,
1126 'version': version,
1126 'version': version,
1127 'csrf_token': CSRF_TOKEN
1127 'csrf_token': CSRF_TOKEN
1128 };
1128 };
1129
1129
1130 var submitSuccessCallback = function(json_data) {
1130 var submitSuccessCallback = function(json_data) {
1131 $editForm.remove();
1131 $editForm.remove();
1132 $comment.show();
1132 $comment.show();
1133 var postData = {
1133 var postData = {
1134 'text': text,
1134 'text': text,
1135 'renderer': $comment.attr('data-comment-renderer'),
1135 'renderer': $comment.attr('data-comment-renderer'),
1136 'csrf_token': CSRF_TOKEN
1136 'csrf_token': CSRF_TOKEN
1137 };
1137 };
1138
1138
1139 /* Inject new edited version selector */
1139 /* Inject new edited version selector */
1140 var updateCommentVersionDropDown = function () {
1140 var updateCommentVersionDropDown = function () {
1141 var versionSelectId = '#comment_versions_'+comment_id;
1141 var versionSelectId = '#comment_versions_'+comment_id;
1142 var preLoadVersionData = [
1142 var preLoadVersionData = [
1143 {
1143 {
1144 id: json_data['comment_version'],
1144 id: json_data['comment_version'],
1145 text: "v{0}".format(json_data['comment_version']),
1145 text: "v{0}".format(json_data['comment_version']),
1146 action: function () {
1146 action: function () {
1147 Rhodecode.comments.showVersion(
1147 Rhodecode.comments.showVersion(
1148 json_data['comment_id'],
1148 json_data['comment_id'],
1149 json_data['comment_history_id']
1149 json_data['comment_history_id']
1150 )
1150 )
1151 },
1151 },
1152 comment_version: json_data['comment_version'],
1152 comment_version: json_data['comment_version'],
1153 comment_author_username: json_data['comment_author_username'],
1153 comment_author_username: json_data['comment_author_username'],
1154 comment_author_gravatar: json_data['comment_author_gravatar'],
1154 comment_author_gravatar: json_data['comment_author_gravatar'],
1155 comment_created_on: json_data['comment_created_on'],
1155 comment_created_on: json_data['comment_created_on'],
1156 },
1156 },
1157 ]
1157 ]
1158
1158
1159
1159
1160 if ($(versionSelectId).data('select2')) {
1160 if ($(versionSelectId).data('select2')) {
1161 var oldData = $(versionSelectId).data('select2').opts.data.results;
1161 var oldData = $(versionSelectId).data('select2').opts.data.results;
1162 $(versionSelectId).select2("destroy");
1162 $(versionSelectId).select2("destroy");
1163 preLoadVersionData = oldData.concat(preLoadVersionData)
1163 preLoadVersionData = oldData.concat(preLoadVersionData)
1164 }
1164 }
1165
1165
1166 initVersionSelector(versionSelectId, {results: preLoadVersionData});
1166 initVersionSelector(versionSelectId, {results: preLoadVersionData});
1167
1167
1168 $comment.attr('data-comment-text', utf8ToB64(text));
1168 $comment.attr('data-comment-text', utf8ToB64(text));
1169
1169
1170 var versionSelector = $('#comment_versions_'+comment_id);
1170 var versionSelector = $('#comment_versions_'+comment_id);
1171
1171
1172 // set lastVersion so we know our last edit version
1172 // set lastVersion so we know our last edit version
1173 versionSelector.data('lastVersion', json_data['comment_version'])
1173 versionSelector.data('lastVersion', json_data['comment_version'])
1174 versionSelector.parent().show();
1174 versionSelector.parent().show();
1175 }
1175 }
1176 updateCommentVersionDropDown();
1176 updateCommentVersionDropDown();
1177
1177
1178 // by default we reset state of comment preserving the text
1178 // by default we reset state of comment preserving the text
1179 var failRenderCommit = function(jqXHR, textStatus, errorThrown) {
1179 var failRenderCommit = function(jqXHR, textStatus, errorThrown) {
1180 var prefix = "Error while editing this comment.\n"
1180 var prefix = "Error while editing this comment.\n"
1181 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1181 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1182 ajaxErrorSwal(message);
1182 ajaxErrorSwal(message);
1183 };
1183 };
1184
1184
1185 var successRenderCommit = function(o){
1185 var successRenderCommit = function(o){
1186 $comment.show();
1186 $comment.show();
1187 $comment[0].lastElementChild.innerHTML = o;
1187 $comment[0].lastElementChild.innerHTML = o;
1188 };
1188 };
1189
1189
1190 var previewUrl = pyroutes.url(
1190 var previewUrl = pyroutes.url(
1191 'repo_commit_comment_preview',
1191 'repo_commit_comment_preview',
1192 {'repo_name': templateContext.repo_name,
1192 {'repo_name': templateContext.repo_name,
1193 'commit_id': templateContext.commit_data.commit_id});
1193 'commit_id': templateContext.commit_data.commit_id});
1194
1194
1195 _submitAjaxPOST(
1195 _submitAjaxPOST(
1196 previewUrl, postData, successRenderCommit, failRenderCommit
1196 previewUrl, postData, successRenderCommit, failRenderCommit
1197 );
1197 );
1198
1198
1199 try {
1199 try {
1200 var html = json_data.rendered_text;
1200 var html = json_data.rendered_text;
1201 var lineno = json_data.line_no;
1201 var lineno = json_data.line_no;
1202 var target_id = json_data.target_id;
1202 var target_id = json_data.target_id;
1203
1203
1204 $comments.find('.cb-comment-add-button').before(html);
1204 $comments.find('.cb-comment-add-button').before(html);
1205
1205
1206 // run global callback on submit
1206 // run global callback on submit
1207 commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
1207 commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
1208
1208
1209 } catch (e) {
1209 } catch (e) {
1210 console.error(e);
1210 console.error(e);
1211 }
1211 }
1212
1212
1213 // re trigger the linkification of next/prev navigation
1213 // re trigger the linkification of next/prev navigation
1214 linkifyComments($('.inline-comment-injected'));
1214 linkifyComments($('.inline-comment-injected'));
1215 timeagoActivate();
1215 timeagoActivate();
1216 tooltipActivate();
1216 tooltipActivate();
1217
1217
1218 if (window.updateSticky !== undefined) {
1218 if (window.updateSticky !== undefined) {
1219 // potentially our comments change the active window size, so we
1219 // potentially our comments change the active window size, so we
1220 // notify sticky elements
1220 // notify sticky elements
1221 updateSticky()
1221 updateSticky()
1222 }
1222 }
1223
1223
1224 if (window.refreshAllComments !== undefined && !isDraft) {
1224 if (window.refreshAllComments !== undefined && !isDraft) {
1225 // if we have this handler, run it, and refresh all comments boxes
1225 // if we have this handler, run it, and refresh all comments boxes
1226 refreshAllComments()
1226 refreshAllComments()
1227 }
1227 }
1228 else if (window.refreshDraftComments !== undefined && isDraft) {
1228 else if (window.refreshDraftComments !== undefined && isDraft) {
1229 // if we have this handler, run it, and refresh all comments boxes
1229 // if we have this handler, run it, and refresh all comments boxes
1230 refreshDraftComments();
1230 refreshDraftComments();
1231 }
1231 }
1232
1232
1233 commentForm.setActionButtonsDisabled(false);
1233 commentForm.setActionButtonsDisabled(false);
1234
1234
1235 };
1235 };
1236
1236
1237 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1237 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1238 var prefix = "Error while editing comment.\n"
1238 var prefix = "Error while editing comment.\n"
1239 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1239 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1240 if (jqXHR.status == 409){
1240 if (jqXHR.status == 409){
1241 message = 'This comment was probably changed somewhere else. Please reload the content of this comment.'
1241 message = 'This comment was probably changed somewhere else. Please reload the content of this comment.'
1242 ajaxErrorSwal(message, 'Comment version mismatch.');
1242 ajaxErrorSwal(message, 'Comment version mismatch.');
1243 } else {
1243 } else {
1244 ajaxErrorSwal(message);
1244 ajaxErrorSwal(message);
1245 }
1245 }
1246
1246
1247 commentForm.resetCommentFormState(text)
1247 commentForm.resetCommentFormState(text)
1248 };
1248 };
1249 commentForm.submitAjaxPOST(
1249 commentForm.submitAjaxPOST(
1250 commentForm.submitUrl, postData,
1250 commentForm.submitUrl, postData,
1251 submitSuccessCallback,
1251 submitSuccessCallback,
1252 submitFailCallback);
1252 submitFailCallback);
1253 });
1253 });
1254 }
1254 }
1255
1255
1256 $editForm.addClass('comment-inline-form-open');
1256 $editForm.addClass('comment-inline-form-open');
1257 };
1257 };
1258
1258
1259 this.attachComment = function(json_data) {
1259 this.attachComment = function(json_data) {
1260 var self = this;
1260 var self = this;
1261 $.each(json_data, function(idx, val) {
1261 $.each(json_data, function(idx, val) {
1262 var json_data_elem = [val]
1262 var json_data_elem = [val]
1263 var isInline = val.comment_f_path && val.comment_lineno
1263 var isInline = val.comment_f_path && val.comment_lineno
1264
1264
1265 if (isInline) {
1265 if (isInline) {
1266 self.attachInlineComment(json_data_elem)
1266 self.attachInlineComment(json_data_elem)
1267 } else {
1267 } else {
1268 self.attachGeneralComment(json_data_elem)
1268 self.attachGeneralComment(json_data_elem)
1269 }
1269 }
1270 })
1270 })
1271
1271
1272 }
1272 }
1273
1273
1274 this.attachGeneralComment = function(json_data) {
1274 this.attachGeneralComment = function(json_data) {
1275 $.each(json_data, function(idx, val) {
1275 $.each(json_data, function(idx, val) {
1276 $('#injected_page_comments').append(val.rendered_text);
1276 $('#injected_page_comments').append(val.rendered_text);
1277 })
1277 })
1278 }
1278 }
1279
1279
1280 this.attachInlineComment = function(json_data) {
1280 this.attachInlineComment = function(json_data) {
1281
1281
1282 $.each(json_data, function (idx, val) {
1282 $.each(json_data, function (idx, val) {
1283 var line_qry = '*[data-line-no="{0}"]'.format(val.line_no);
1283 var line_qry = '*[data-line-no="{0}"]'.format(val.line_no);
1284 var html = val.rendered_text;
1284 var html = val.rendered_text;
1285 var $inlineComments = $('#' + val.target_id)
1285 var $inlineComments = $('#' + val.target_id)
1286 .find(line_qry)
1286 .find(line_qry)
1287 .find('.inline-comments');
1287 .find('.inline-comments');
1288
1288
1289 var lastComment = $inlineComments.find('.comment-inline').last();
1289 var lastComment = $inlineComments.find('.comment-inline').last();
1290
1290
1291 if (lastComment.length === 0) {
1291 if (lastComment.length === 0) {
1292 // first comment, we append simply
1292 // first comment, we append simply
1293 $inlineComments.find('.reply-thread-container-wrapper').before(html);
1293 $inlineComments.find('.reply-thread-container-wrapper').before(html);
1294 } else {
1294 } else {
1295 $(lastComment).after(html)
1295 $(lastComment).after(html)
1296 }
1296 }
1297
1297
1298 })
1298 })
1299
1299
1300 };
1300 };
1301
1301
1302 this.createNewFormWrapper = function(f_path, line_no) {
1302 this.createNewFormWrapper = function(f_path, line_no) {
1303 // create a new reply HTML form from template
1303 // create a new reply HTML form from template
1304 var tmpl = $('#cb-comment-inline-form-template').html();
1304 var tmpl = $('#cb-comment-inline-form-template').html();
1305 tmpl = tmpl.format(escapeHtml(f_path), line_no);
1305 tmpl = tmpl.format(escapeHtml(f_path), line_no);
1306 return $(tmpl);
1306 return $(tmpl);
1307 }
1307 }
1308
1308
1309 this.markCommentResolved = function(commentId) {
1309 this.markCommentResolved = function(commentId) {
1310 $('#comment-label-{0}'.format(commentId)).find('.resolved').show();
1310 $('#comment-label-{0}'.format(commentId)).find('.resolved').show();
1311 $('#comment-label-{0}'.format(commentId)).find('.resolve').hide();
1311 $('#comment-label-{0}'.format(commentId)).find('.resolve').hide();
1312 };
1312 };
1313
1313
1314 this.createComment = function(node, f_path, line_no, resolutionComment) {
1314 this.createComment = function(node, f_path, line_no, resolutionComment) {
1315 self.edit = false;
1315 self.edit = false;
1316 var $node = $(node);
1316 var $node = $(node);
1317 var $td = $node.closest('td');
1317 var $td = $node.closest('td');
1318 var resolvesCommentId = resolutionComment || null;
1318 var resolvesCommentId = resolutionComment || null;
1319
1319
1320 var $replyForm = $td.find('.comment-inline-form');
1320 var $replyForm = $td.find('.comment-inline-form');
1321
1321
1322 // if form isn't existing, we're generating a new one and injecting it.
1322 // if form isn't existing, we're generating a new one and injecting it.
1323 if ($replyForm.length === 0) {
1323 if ($replyForm.length === 0) {
1324
1324
1325 // unhide/expand all comments if they are hidden for a proper REPLY mode
1325 // unhide/expand all comments if they are hidden for a proper REPLY mode
1326 self.toggleLineComments($node, true);
1326 self.toggleLineComments($node, true);
1327
1327
1328 $replyForm = self.createNewFormWrapper(f_path, line_no);
1328 $replyForm = self.createNewFormWrapper(f_path, line_no);
1329
1329
1330 var $comments = $td.find('.inline-comments');
1330 var $comments = $td.find('.inline-comments');
1331
1331
1332 // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
1332 // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
1333 if ($comments.length===0) {
1333 if ($comments.length===0) {
1334 var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(f_path, line_no)
1334 var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(escapeHtml(f_path), line_no)
1335 var $reply_container = $('#cb-comments-inline-container-template')
1335 var $reply_container = $('#cb-comments-inline-container-template')
1336 $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
1336 $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
1337 $td.append($($reply_container).html());
1337 $td.append($($reply_container).html());
1338 }
1338 }
1339
1339
1340 // default comment button exists, so we prepend the form for leaving initial comment
1340 // default comment button exists, so we prepend the form for leaving initial comment
1341 $td.find('.cb-comment-add-button').before($replyForm);
1341 $td.find('.cb-comment-add-button').before($replyForm);
1342 // set marker, that we have a open form
1342 // set marker, that we have a open form
1343 var $replyWrapper = $td.find('.reply-thread-container-wrapper')
1343 var $replyWrapper = $td.find('.reply-thread-container-wrapper')
1344 $replyWrapper.addClass('comment-form-active');
1344 $replyWrapper.addClass('comment-form-active');
1345
1345
1346 var lastComment = $comments.find('.comment-inline').last();
1346 var lastComment = $comments.find('.comment-inline').last();
1347 if ($(lastComment).hasClass('comment-outdated')) {
1347 if ($(lastComment).hasClass('comment-outdated')) {
1348 $replyWrapper.show();
1348 $replyWrapper.show();
1349 }
1349 }
1350
1350
1351 var _form = $($replyForm[0]).find('form');
1351 var _form = $($replyForm[0]).find('form');
1352 var autocompleteActions = ['as_note', 'as_todo'];
1352 var autocompleteActions = ['as_note', 'as_todo'];
1353 var comment_id=null;
1353 var comment_id=null;
1354 var placeholderText = _gettext('Leave a comment on file {0} line {1}.').format(f_path, line_no);
1354 var placeholderText = _gettext('Leave a comment on file {0} line {1}.').format(f_path, line_no);
1355 var commentForm = self.createCommentForm(
1355 var commentForm = self.createCommentForm(
1356 _form, line_no, placeholderText, autocompleteActions, resolvesCommentId,
1356 _form, line_no, placeholderText, autocompleteActions, resolvesCommentId,
1357 self.edit, comment_id);
1357 self.edit, comment_id);
1358
1358
1359 // set a CUSTOM submit handler for inline comments.
1359 // set a CUSTOM submit handler for inline comments.
1360 commentForm.setHandleFormSubmit(function(o) {
1360 commentForm.setHandleFormSubmit(function(o) {
1361 var text = commentForm.cm.getValue();
1361 var text = commentForm.cm.getValue();
1362 var commentType = commentForm.getCommentType();
1362 var commentType = commentForm.getCommentType();
1363 var resolvesCommentId = commentForm.getResolvesId();
1363 var resolvesCommentId = commentForm.getResolvesId();
1364 var isDraft = commentForm.getDraftState();
1364 var isDraft = commentForm.getDraftState();
1365
1365
1366 if (text === "") {
1366 if (text === "") {
1367 return;
1367 return;
1368 }
1368 }
1369
1369
1370 if (line_no === undefined) {
1370 if (line_no === undefined) {
1371 alert('Error: unable to fetch line number for this inline comment !');
1371 alert('Error: unable to fetch line number for this inline comment !');
1372 return;
1372 return;
1373 }
1373 }
1374
1374
1375 if (f_path === undefined) {
1375 if (f_path === undefined) {
1376 alert('Error: unable to fetch file path for this inline comment !');
1376 alert('Error: unable to fetch file path for this inline comment !');
1377 return;
1377 return;
1378 }
1378 }
1379
1379
1380 var excludeCancelBtn = false;
1380 var excludeCancelBtn = false;
1381 var submitEvent = true;
1381 var submitEvent = true;
1382 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1382 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1383 commentForm.cm.setOption("readOnly", true);
1383 commentForm.cm.setOption("readOnly", true);
1384 var postData = {
1384 var postData = {
1385 'text': text,
1385 'text': text,
1386 'f_path': f_path,
1386 'f_path': f_path,
1387 'line': line_no,
1387 'line': line_no,
1388 'comment_type': commentType,
1388 'comment_type': commentType,
1389 'draft': isDraft,
1389 'draft': isDraft,
1390 'csrf_token': CSRF_TOKEN
1390 'csrf_token': CSRF_TOKEN
1391 };
1391 };
1392 if (resolvesCommentId){
1392 if (resolvesCommentId){
1393 postData['resolves_comment_id'] = resolvesCommentId;
1393 postData['resolves_comment_id'] = resolvesCommentId;
1394 }
1394 }
1395
1395
1396 // submitSuccess for inline commits
1396 // submitSuccess for inline commits
1397 var submitSuccessCallback = function(json_data) {
1397 var submitSuccessCallback = function(json_data) {
1398
1398
1399 $replyForm.remove();
1399 $replyForm.remove();
1400 $td.find('.reply-thread-container-wrapper').removeClass('comment-form-active');
1400 $td.find('.reply-thread-container-wrapper').removeClass('comment-form-active');
1401
1401
1402 try {
1402 try {
1403
1403
1404 // inject newly created comments, json_data is {<comment_id>: {}}
1404 // inject newly created comments, json_data is {<comment_id>: {}}
1405 self.attachInlineComment(json_data)
1405 self.attachInlineComment(json_data)
1406
1406
1407 //mark visually which comment was resolved
1407 //mark visually which comment was resolved
1408 if (resolvesCommentId) {
1408 if (resolvesCommentId) {
1409 self.markCommentResolved(resolvesCommentId);
1409 self.markCommentResolved(resolvesCommentId);
1410 }
1410 }
1411
1411
1412 // run global callback on submit
1412 // run global callback on submit
1413 commentForm.globalSubmitSuccessCallback({
1413 commentForm.globalSubmitSuccessCallback({
1414 draft: isDraft,
1414 draft: isDraft,
1415 comment_id: comment_id
1415 comment_id: comment_id
1416 });
1416 });
1417
1417
1418 } catch (e) {
1418 } catch (e) {
1419 console.error(e);
1419 console.error(e);
1420 }
1420 }
1421
1421
1422 if (window.updateSticky !== undefined) {
1422 if (window.updateSticky !== undefined) {
1423 // potentially our comments change the active window size, so we
1423 // potentially our comments change the active window size, so we
1424 // notify sticky elements
1424 // notify sticky elements
1425 updateSticky()
1425 updateSticky()
1426 }
1426 }
1427
1427
1428 if (window.refreshAllComments !== undefined && !isDraft) {
1428 if (window.refreshAllComments !== undefined && !isDraft) {
1429 // if we have this handler, run it, and refresh all comments boxes
1429 // if we have this handler, run it, and refresh all comments boxes
1430 refreshAllComments()
1430 refreshAllComments()
1431 }
1431 }
1432 else if (window.refreshDraftComments !== undefined && isDraft) {
1432 else if (window.refreshDraftComments !== undefined && isDraft) {
1433 // if we have this handler, run it, and refresh all comments boxes
1433 // if we have this handler, run it, and refresh all comments boxes
1434 refreshDraftComments();
1434 refreshDraftComments();
1435 }
1435 }
1436
1436
1437 commentForm.setActionButtonsDisabled(false);
1437 commentForm.setActionButtonsDisabled(false);
1438
1438
1439 // re trigger the linkification of next/prev navigation
1439 // re trigger the linkification of next/prev navigation
1440 linkifyComments($('.inline-comment-injected'));
1440 linkifyComments($('.inline-comment-injected'));
1441 timeagoActivate();
1441 timeagoActivate();
1442 tooltipActivate();
1442 tooltipActivate();
1443 };
1443 };
1444
1444
1445 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1445 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1446 var prefix = "Error while submitting comment.\n"
1446 var prefix = "Error while submitting comment.\n"
1447 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1447 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1448 ajaxErrorSwal(message);
1448 ajaxErrorSwal(message);
1449 commentForm.resetCommentFormState(text)
1449 commentForm.resetCommentFormState(text)
1450 };
1450 };
1451
1451
1452 commentForm.submitAjaxPOST(
1452 commentForm.submitAjaxPOST(
1453 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1453 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1454 });
1454 });
1455 }
1455 }
1456
1456
1457 // Finally "open" our reply form, since we know there are comments and we have the "attached" old form
1457 // Finally "open" our reply form, since we know there are comments and we have the "attached" old form
1458 $replyForm.addClass('comment-inline-form-open');
1458 $replyForm.addClass('comment-inline-form-open');
1459 tooltipActivate();
1459 tooltipActivate();
1460 };
1460 };
1461
1461
1462 this.createResolutionComment = function(commentId){
1462 this.createResolutionComment = function(commentId){
1463 // hide the trigger text
1463 // hide the trigger text
1464 $('#resolve-comment-{0}'.format(commentId)).hide();
1464 $('#resolve-comment-{0}'.format(commentId)).hide();
1465
1465
1466 var comment = $('#comment-'+commentId);
1466 var comment = $('#comment-'+commentId);
1467 var commentData = comment.data();
1467 var commentData = comment.data();
1468
1468
1469 if (commentData.commentInline) {
1469 if (commentData.commentInline) {
1470 var f_path = commentData.commentFPath;
1470 var f_path = commentData.commentFPath;
1471 var line_no = commentData.commentLineNo;
1471 var line_no = commentData.commentLineNo;
1472 this.createComment(comment, f_path, line_no, commentId)
1472 this.createComment(comment, f_path, line_no, commentId)
1473 } else {
1473 } else {
1474 this.createGeneralComment('general', "$placeholder", commentId)
1474 this.createGeneralComment('general', "$placeholder", commentId)
1475 }
1475 }
1476
1476
1477 return false;
1477 return false;
1478 };
1478 };
1479
1479
1480 this.submitResolution = function(commentId){
1480 this.submitResolution = function(commentId){
1481 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
1481 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
1482 var commentForm = form.get(0).CommentForm;
1482 var commentForm = form.get(0).CommentForm;
1483
1483
1484 var cm = commentForm.getCmInstance();
1484 var cm = commentForm.getCmInstance();
1485 var renderer = templateContext.visual.default_renderer;
1485 var renderer = templateContext.visual.default_renderer;
1486 if (renderer == 'rst'){
1486 if (renderer == 'rst'){
1487 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
1487 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
1488 } else if (renderer == 'markdown') {
1488 } else if (renderer == 'markdown') {
1489 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
1489 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
1490 } else {
1490 } else {
1491 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
1491 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
1492 }
1492 }
1493
1493
1494 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
1494 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
1495 form.submit();
1495 form.submit();
1496 return false;
1496 return false;
1497 };
1497 };
1498
1498
1499 this.resolveTodo = function (elem, todoId) {
1499 this.resolveTodo = function (elem, todoId) {
1500 var commentId = todoId;
1500 var commentId = todoId;
1501
1501
1502 SwalNoAnimation.fire({
1502 SwalNoAnimation.fire({
1503 title: 'Resolve TODO {0}'.format(todoId),
1503 title: 'Resolve TODO {0}'.format(todoId),
1504 showCancelButton: true,
1504 showCancelButton: true,
1505 confirmButtonText: _gettext('Yes'),
1505 confirmButtonText: _gettext('Yes'),
1506 showLoaderOnConfirm: true,
1506 showLoaderOnConfirm: true,
1507
1507
1508 allowOutsideClick: function () {
1508 allowOutsideClick: function () {
1509 !Swal.isLoading()
1509 !Swal.isLoading()
1510 },
1510 },
1511 preConfirm: function () {
1511 preConfirm: function () {
1512 var comment = $('#comment-' + commentId);
1512 var comment = $('#comment-' + commentId);
1513 var commentData = comment.data();
1513 var commentData = comment.data();
1514
1514
1515 var f_path = null
1515 var f_path = null
1516 var line_no = null
1516 var line_no = null
1517 if (commentData.commentInline) {
1517 if (commentData.commentInline) {
1518 f_path = commentData.commentFPath;
1518 f_path = commentData.commentFPath;
1519 line_no = commentData.commentLineNo;
1519 line_no = commentData.commentLineNo;
1520 }
1520 }
1521
1521
1522 var renderer = templateContext.visual.default_renderer;
1522 var renderer = templateContext.visual.default_renderer;
1523 var commentBoxUrl = '{1}#comment-{0}'.format(commentId);
1523 var commentBoxUrl = '{1}#comment-{0}'.format(commentId);
1524
1524
1525 // Pull request case
1525 // Pull request case
1526 if (templateContext.pull_request_data.pull_request_id !== null) {
1526 if (templateContext.pull_request_data.pull_request_id !== null) {
1527 var commentUrl = pyroutes.url('pullrequest_comment_create',
1527 var commentUrl = pyroutes.url('pullrequest_comment_create',
1528 {
1528 {
1529 'repo_name': templateContext.repo_name,
1529 'repo_name': templateContext.repo_name,
1530 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1530 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1531 'comment_id': commentId
1531 'comment_id': commentId
1532 });
1532 });
1533 } else {
1533 } else {
1534 var commentUrl = pyroutes.url('repo_commit_comment_create',
1534 var commentUrl = pyroutes.url('repo_commit_comment_create',
1535 {
1535 {
1536 'repo_name': templateContext.repo_name,
1536 'repo_name': templateContext.repo_name,
1537 'commit_id': templateContext.commit_data.commit_id,
1537 'commit_id': templateContext.commit_data.commit_id,
1538 'comment_id': commentId
1538 'comment_id': commentId
1539 });
1539 });
1540 }
1540 }
1541
1541
1542 if (renderer === 'rst') {
1542 if (renderer === 'rst') {
1543 commentBoxUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentUrl);
1543 commentBoxUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentUrl);
1544 } else if (renderer === 'markdown') {
1544 } else if (renderer === 'markdown') {
1545 commentBoxUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentUrl);
1545 commentBoxUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentUrl);
1546 }
1546 }
1547 var resolveText = _gettext('TODO from comment {0} was fixed.').format(commentBoxUrl);
1547 var resolveText = _gettext('TODO from comment {0} was fixed.').format(commentBoxUrl);
1548
1548
1549 var postData = {
1549 var postData = {
1550 text: resolveText,
1550 text: resolveText,
1551 comment_type: 'note',
1551 comment_type: 'note',
1552 draft: false,
1552 draft: false,
1553 csrf_token: CSRF_TOKEN,
1553 csrf_token: CSRF_TOKEN,
1554 resolves_comment_id: commentId
1554 resolves_comment_id: commentId
1555 }
1555 }
1556 if (commentData.commentInline) {
1556 if (commentData.commentInline) {
1557 postData['f_path'] = f_path;
1557 postData['f_path'] = f_path;
1558 postData['line'] = line_no;
1558 postData['line'] = line_no;
1559 }
1559 }
1560
1560
1561 return new Promise(function (resolve, reject) {
1561 return new Promise(function (resolve, reject) {
1562 $.ajax({
1562 $.ajax({
1563 type: 'POST',
1563 type: 'POST',
1564 data: postData,
1564 data: postData,
1565 url: commentUrl,
1565 url: commentUrl,
1566 headers: {'X-PARTIAL-XHR': true}
1566 headers: {'X-PARTIAL-XHR': true}
1567 })
1567 })
1568 .done(function (data) {
1568 .done(function (data) {
1569 resolve(data);
1569 resolve(data);
1570 })
1570 })
1571 .fail(function (jqXHR, textStatus, errorThrown) {
1571 .fail(function (jqXHR, textStatus, errorThrown) {
1572 var prefix = "Error while resolving TODO.\n"
1572 var prefix = "Error while resolving TODO.\n"
1573 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1573 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1574 ajaxErrorSwal(message);
1574 ajaxErrorSwal(message);
1575 });
1575 });
1576 })
1576 })
1577 }
1577 }
1578
1578
1579 })
1579 })
1580 .then(function (result) {
1580 .then(function (result) {
1581 var success = function (json_data) {
1581 var success = function (json_data) {
1582 resolvesCommentId = commentId;
1582 resolvesCommentId = commentId;
1583 var commentResolved = json_data[Object.keys(json_data)[0]]
1583 var commentResolved = json_data[Object.keys(json_data)[0]]
1584
1584
1585 try {
1585 try {
1586
1586
1587 if (commentResolved.f_path) {
1587 if (commentResolved.f_path) {
1588 // inject newly created comments, json_data is {<comment_id>: {}}
1588 // inject newly created comments, json_data is {<comment_id>: {}}
1589 self.attachInlineComment(json_data)
1589 self.attachInlineComment(json_data)
1590 } else {
1590 } else {
1591 self.attachGeneralComment(json_data)
1591 self.attachGeneralComment(json_data)
1592 }
1592 }
1593
1593
1594 //mark visually which comment was resolved
1594 //mark visually which comment was resolved
1595 if (resolvesCommentId) {
1595 if (resolvesCommentId) {
1596 self.markCommentResolved(resolvesCommentId);
1596 self.markCommentResolved(resolvesCommentId);
1597 }
1597 }
1598
1598
1599 // run global callback on submit
1599 // run global callback on submit
1600 if (window.commentFormGlobalSubmitSuccessCallback !== undefined) {
1600 if (window.commentFormGlobalSubmitSuccessCallback !== undefined) {
1601 commentFormGlobalSubmitSuccessCallback({
1601 commentFormGlobalSubmitSuccessCallback({
1602 draft: false,
1602 draft: false,
1603 comment_id: commentId
1603 comment_id: commentId
1604 });
1604 });
1605 }
1605 }
1606
1606
1607 } catch (e) {
1607 } catch (e) {
1608 console.error(e);
1608 console.error(e);
1609 }
1609 }
1610
1610
1611 if (window.updateSticky !== undefined) {
1611 if (window.updateSticky !== undefined) {
1612 // potentially our comments change the active window size, so we
1612 // potentially our comments change the active window size, so we
1613 // notify sticky elements
1613 // notify sticky elements
1614 updateSticky()
1614 updateSticky()
1615 }
1615 }
1616
1616
1617 if (window.refreshAllComments !== undefined) {
1617 if (window.refreshAllComments !== undefined) {
1618 // if we have this handler, run it, and refresh all comments boxes
1618 // if we have this handler, run it, and refresh all comments boxes
1619 refreshAllComments()
1619 refreshAllComments()
1620 }
1620 }
1621 // re trigger the linkification of next/prev navigation
1621 // re trigger the linkification of next/prev navigation
1622 linkifyComments($('.inline-comment-injected'));
1622 linkifyComments($('.inline-comment-injected'));
1623 timeagoActivate();
1623 timeagoActivate();
1624 tooltipActivate();
1624 tooltipActivate();
1625 };
1625 };
1626
1626
1627 if (result.value) {
1627 if (result.value) {
1628 $(elem).remove();
1628 $(elem).remove();
1629 success(result.value)
1629 success(result.value)
1630 }
1630 }
1631 })
1631 })
1632 };
1632 };
1633
1633
1634 };
1634 };
1635
1635
1636 window.commentHelp = function(renderer) {
1636 window.commentHelp = function(renderer) {
1637 var funcData = {'renderer': renderer}
1637 var funcData = {'renderer': renderer}
1638 return renderTemplate('commentHelpHovercard', funcData)
1638 return renderTemplate('commentHelpHovercard', funcData)
1639 }
1639 }
@@ -1,278 +1,278 b''
1 <%text>
1 <%text>
2 <div style="display: none">
2 <div style="display: none">
3
3
4 <script>
4 <script>
5 var CG = new ColorGenerator();
5 var CG = new ColorGenerator();
6 </script>
6 </script>
7
7
8 <script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate">
8 <script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate">
9
9
10 <%
10 <%
11 if (size > 16) {
11 if (size > 16) {
12 var gravatar_class = 'gravatar gravatar-large';
12 var gravatar_class = 'gravatar gravatar-large';
13 } else {
13 } else {
14 var gravatar_class = 'gravatar';
14 var gravatar_class = 'gravatar';
15 }
15 }
16
16
17 if (tooltip) {
17 if (tooltip) {
18 var gravatar_class = gravatar_class + ' tooltip-hovercard';
18 var gravatar_class = gravatar_class + ' tooltip-hovercard';
19 }
19 }
20
20
21 var data_hovercard_alt = username;
21 var data_hovercard_alt = username;
22
22
23 %>
23 %>
24
24
25 <%
25 <%
26 if (show_disabled) {
26 if (show_disabled) {
27 var user_cls = 'user user-disabled';
27 var user_cls = 'user user-disabled';
28 } else {
28 } else {
29 var user_cls = 'user';
29 var user_cls = 'user';
30 }
30 }
31 var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id})
31 var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id})
32 %>
32 %>
33
33
34 <div class="rc-user">
34 <div class="rc-user">
35 <img class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" data-hovercard-url="<%= data_hovercard_url %>" data-hovercard-alt="<%= data_hovercard_alt %>" src="<%- gravatar_url -%>">
35 <img class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" data-hovercard-url="<%= data_hovercard_url %>" data-hovercard-alt="<%= data_hovercard_alt %>" src="<%- gravatar_url -%>">
36 <span class="<%= user_cls %>"> <%- user_link -%> </span>
36 <span class="<%= user_cls %>"> <%- user_link -%> </span>
37 </div>
37 </div>
38
38
39 </script>
39 </script>
40
40
41 <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate">
41 <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate">
42 <%
42 <%
43 if (create) {
43 if (create) {
44 var edit_visibility = 'visible';
44 var edit_visibility = 'visible';
45 } else {
45 } else {
46 var edit_visibility = 'hidden';
46 var edit_visibility = 'hidden';
47 }
47 }
48
48
49 if (member.user_group && member.user_group.vote_rule) {
49 if (member.user_group && member.user_group.vote_rule) {
50 var reviewGroup = '<i class="icon-user-group"></i>';
50 var reviewGroup = '<i class="icon-user-group"></i>';
51 var reviewGroupColor = CG.asRGB(CG.getColor(member.user_group.vote_rule));
51 var reviewGroupColor = CG.asRGB(CG.getColor(member.user_group.vote_rule));
52 } else {
52 } else {
53 var reviewGroup = null;
53 var reviewGroup = null;
54 var reviewGroupColor = 'transparent';
54 var reviewGroupColor = 'transparent';
55 }
55 }
56 var rule_show = rule_show || false;
56 var rule_show = rule_show || false;
57
57
58 if (rule_show) {
58 if (rule_show) {
59 var rule_visibility = 'table-cell';
59 var rule_visibility = 'table-cell';
60 } else {
60 } else {
61 var rule_visibility = 'none';
61 var rule_visibility = 'none';
62 }
62 }
63
63
64 %>
64 %>
65
65
66 <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>">
66 <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>">
67
67
68 <% if (create) { %>
68 <% if (create) { %>
69 <td style="width: 1px"></td>
69 <td style="width: 1px"></td>
70 <% } else { %>
70 <% } else { %>
71 <td style="width: 20px">
71 <td style="width: 20px">
72 <div class="tooltip presence-state" style="display: none; position: absolute; left: 2px" title="This users is currently at this page">
72 <div class="tooltip presence-state" style="display: none; position: absolute; left: 2px" title="This users is currently at this page">
73 <i class="icon-eye" style="color: #0ac878"></i>
73 <i class="icon-eye" style="color: #0ac878"></i>
74 </div>
74 </div>
75 <% if (role === 'reviewer') { %>
75 <% if (role === 'reviewer') { %>
76 <div class="reviewer_status tooltip" title="<%= review_status_label %>">
76 <div class="reviewer_status tooltip" title="<%= review_status_label %>">
77 <i class="icon-circle review-status-<%= review_status %>"></i>
77 <i class="icon-circle review-status-<%= review_status %>"></i>
78 </div>
78 </div>
79 <% } else if (role === 'observer') { %>
79 <% } else if (role === 'observer') { %>
80 <div class="tooltip" title="Observer without voting right.">
80 <div class="tooltip" title="Observer without voting right.">
81 <i class="icon-circle-thin"></i>
81 <i class="icon-circle-thin"></i>
82 </div>
82 </div>
83 <% } %>
83 <% } %>
84 </td>
84 </td>
85 <% } %>
85 <% } %>
86
86
87
87
88 <% if (mandatory) { %>
88 <% if (mandatory) { %>
89 <td style="text-align: right;width: 10px;">
89 <td style="text-align: right;width: 10px;">
90 <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer">
90 <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer">
91 <i class="icon-lock"></i>
91 <i class="icon-lock"></i>
92 </div>
92 </div>
93 </td>
93 </td>
94
94
95 <% } else { %>
95 <% } else { %>
96 <td style="text-align: right;width: 10px;">
96 <td style="text-align: right;width: 10px;">
97 <% if (allowed_to_update) { %>
97 <% if (allowed_to_update) { %>
98 <div class="<%=role %>_member_remove" onclick="reviewersController.removeMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
98 <div class="<%=role %>_member_remove" onclick="reviewersController.removeMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
99 <i class="icon-remove" style="color: #e85e4d;"></i>
99 <i class="icon-remove" style="color: #e85e4d;"></i>
100 </div>
100 </div>
101 <% } %>
101 <% } %>
102 </td>
102 </td>
103 <% } %>
103 <% } %>
104
104
105 <td>
105 <td>
106 <div id="reviewer_<%= member.user_id %>_name" class="reviewer_name">
106 <div id="reviewer_<%= member.user_id %>_name" class="reviewer_name">
107 <%-
107 <%-
108 renderTemplate('gravatarWithUser', {
108 renderTemplate('gravatarWithUser', {
109 'size': 16,
109 'size': 16,
110 'show_disabled': false,
110 'show_disabled': false,
111 'tooltip': true,
111 'tooltip': true,
112 'username': member.username,
112 'username': member.username,
113 'user_id': member.user_id,
113 'user_id': member.user_id,
114 'user_link': member.user_link,
114 'user_link': member.user_link,
115 'gravatar_url': member.gravatar_link
115 'gravatar_url': member.gravatar_link
116 })
116 })
117 %>
117 %>
118 </div>
118 </div>
119 <% if (reviewGroup !== null) { %>
119 <% if (reviewGroup !== null) { %>
120 <span class="tooltip" title="Member of review group from rule: `<%= member.user_group.name %>`" style="color: <%= reviewGroupColor %>">
120 <span class="tooltip" title="Member of review group from rule: `<%= member.user_group.name %>`" style="color: <%= reviewGroupColor %>">
121 <%- reviewGroup %>
121 <%- reviewGroup %>
122 </span>
122 </span>
123 <% } %>
123 <% } %>
124 </td>
124 </td>
125
125
126 </tr>
126 </tr>
127
127
128 <tr id="reviewer_<%= member.user_id %>_rules">
128 <tr id="reviewer_<%= member.user_id %>_rules">
129 <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container">
129 <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container">
130 <input type="hidden" name="__start__" value="reviewer:mapping">
130 <input type="hidden" name="__start__" value="reviewer:mapping">
131
131
132 <%if (member.user_group && member.user_group.vote_rule) { %>
132 <%if (member.user_group && member.user_group.vote_rule) { %>
133 <div class="reviewer_reason">
133 <div class="reviewer_reason">
134
134
135 <%if (member.user_group.vote_rule == -1) {%>
135 <%if (member.user_group.vote_rule == -1) {%>
136 - group votes required: ALL
136 - group votes required: ALL
137 <%} else {%>
137 <%} else {%>
138 - group votes required: <%= member.user_group.vote_rule %>
138 - group votes required: <%= member.user_group.vote_rule %>
139 <%}%>
139 <%}%>
140 </div>
140 </div>
141 <%} %>
141 <%} %>
142
142
143 <input type="hidden" name="__start__" value="reasons:sequence">
143 <input type="hidden" name="__start__" value="reasons:sequence">
144 <% for (var i = 0; i < reasons.length; i++) { %>
144 <% for (var i = 0; i < reasons.length; i++) { %>
145 <% var reason = reasons[i] %>
145 <% var reason = reasons[i] %>
146 <div class="reviewer_reason">- <%= reason %></div>
146 <div class="reviewer_reason">- <%= reason %></div>
147 <input type="hidden" name="reason" value="<%= reason %>">
147 <input type="hidden" name="reason" value="<%= reason %>">
148 <% } %>
148 <% } %>
149 <input type="hidden" name="__end__" value="reasons:sequence">
149 <input type="hidden" name="__end__" value="reasons:sequence">
150
150
151 <input type="hidden" name="__start__" value="rules:sequence">
151 <input type="hidden" name="__start__" value="rules:sequence">
152 <% for (var i = 0; i < member.rules.length; i++) { %>
152 <% for (var i = 0; i < member.rules.length; i++) { %>
153 <% var rule = member.rules[i] %>
153 <% var rule = member.rules[i] %>
154 <input type="hidden" name="rule_id" value="<%= rule %>">
154 <input type="hidden" name="rule_id" value="<%= rule %>">
155 <% } %>
155 <% } %>
156 <input type="hidden" name="__end__" value="rules:sequence">
156 <input type="hidden" name="__end__" value="rules:sequence">
157
157
158 <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" />
158 <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" />
159 <input type="hidden" name="mandatory" value="<%= mandatory %>"/>
159 <input type="hidden" name="mandatory" value="<%= mandatory %>"/>
160 <input type="hidden" name="role" value="<%= role %>"/>
160 <input type="hidden" name="role" value="<%= role %>"/>
161
161
162 <input type="hidden" name="__end__" value="reviewer:mapping">
162 <input type="hidden" name="__end__" value="reviewer:mapping">
163 </td>
163 </td>
164 </tr>
164 </tr>
165
165
166 </script>
166 </script>
167
167
168 <script id="ejs_commentVersion" type="text/template" class="ejsTemplate">
168 <script id="ejs_commentVersion" type="text/template" class="ejsTemplate">
169
169
170 <%
170 <%
171 if (size > 16) {
171 if (size > 16) {
172 var gravatar_class = 'gravatar gravatar-large';
172 var gravatar_class = 'gravatar gravatar-large';
173 } else {
173 } else {
174 var gravatar_class = 'gravatar';
174 var gravatar_class = 'gravatar';
175 }
175 }
176
176
177 %>
177 %>
178
178
179 <%
179 <%
180 if (show_disabled) {
180 if (show_disabled) {
181 var user_cls = 'user user-disabled';
181 var user_cls = 'user user-disabled';
182 } else {
182 } else {
183 var user_cls = 'user';
183 var user_cls = 'user';
184 }
184 }
185
185
186 %>
186 %>
187
187
188 <div style='line-height: 20px'>
188 <div style='line-height: 20px'>
189 <img style="margin: -3px 0" class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" src="<%- gravatar_url -%>">
189 <img style="margin: -3px 0" class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" src="<%- gravatar_url -%>">
190 <strong><%- user_name -%></strong>, <code>v<%- version -%></code> edited <%- timeago_component -%>
190 <strong><%- user_name -%></strong>, <code>v<%- version -%></code> edited <%- timeago_component -%>
191 </div>
191 </div>
192
192
193 </script>
193 </script>
194
194
195
195
196 <script id="ejs_sideBarCommentHovercard" type="text/template" class="ejsTemplate">
196 <script id="ejs_sideBarCommentHovercard" type="text/template" class="ejsTemplate">
197
197
198 <div>
198 <div>
199
199
200 <% if (is_todo) { %>
200 <% if (is_todo) { %>
201 <% if (inline) { %>
201 <% if (inline) { %>
202 <strong>Inline</strong> TODO (<code>#<%- comment_id -%></code>) on line: <%= line_no %>
202 <strong>Inline</strong> TODO (<code>#<%- comment_id -%></code>) on line: <%= line_no %>
203 <% if (version_info) { %>
203 <% if (version_info) { %>
204 <%= version_info %>
204 <%= version_info %>
205 <% } %>
205 <% } %>
206 <br/>
206 <br/>
207 File: <code><%- file_name -%></code>
207 File: <code><%- file_name -%></code>
208 <% } else { %>
208 <% } else { %>
209 <% if (review_status) { %>
209 <% if (review_status) { %>
210 <i class="icon-circle review-status-<%= review_status %>"></i>
210 <i class="icon-circle review-status-<%= review_status %>"></i>
211 <% } %>
211 <% } %>
212 <strong>General</strong> TODO (<code>#<%- comment_id -%></code>)
212 <strong>General</strong> TODO (<code>#<%- comment_id -%></code>)
213 <% if (version_info) { %>
213 <% if (version_info) { %>
214 <%= version_info %>
214 <%= version_info %>
215 <% } %>
215 <% } %>
216 <% } %>
216 <% } %>
217 <% } else { %>
217 <% } else { %>
218 <% if (inline) { %>
218 <% if (inline) { %>
219 <strong>Inline</strong> comment (<code>#<%- comment_id -%></code>) on line: <%= line_no %>
219 <strong>Inline</strong> comment (<code>#<%- comment_id -%></code>) on line: <%= line_no %>
220 <% if (version_info) { %>
220 <% if (version_info) { %>
221 <%= version_info %>
221 <%= version_info %>
222 <% } %>
222 <% } %>
223 <br/>
223 <br/>
224 File: <code><%- file_name -%></code>
224 File: <code><%= file_name -%></code>
225 <% } else { %>
225 <% } else { %>
226 <% if (review_status) { %>
226 <% if (review_status) { %>
227 <i class="icon-circle review-status-<%= review_status %>"></i>
227 <i class="icon-circle review-status-<%= review_status %>"></i>
228 <% } %>
228 <% } %>
229 <strong>General</strong> comment (<code>#<%- comment_id -%></code>)
229 <strong>General</strong> comment (<code>#<%- comment_id -%></code>)
230 <% if (version_info) { %>
230 <% if (version_info) { %>
231 <%= version_info %>
231 <%= version_info %>
232 <% } %>
232 <% } %>
233 <% } %>
233 <% } %>
234 <% } %>
234 <% } %>
235 <br/>
235 <br/>
236 Created:
236 Created:
237 <time class="timeago" title="<%= created_on %>" datetime="<%= datetime %>"><%= $.timeago(datetime) %></time>
237 <time class="timeago" title="<%= created_on %>" datetime="<%= datetime %>"><%= $.timeago(datetime) %></time>
238
238
239 <% if (is_todo) { %>
239 <% if (is_todo) { %>
240 <div style="text-align: center; padding-top: 5px">
240 <div style="text-align: center; padding-top: 5px">
241 <a class="btn btn-sm" href="#resolveTodo<%- comment_id -%>" onclick="Rhodecode.comments.resolveTodo(this, '<%- comment_id -%>'); return false">
241 <a class="btn btn-sm" href="#resolveTodo<%- comment_id -%>" onclick="Rhodecode.comments.resolveTodo(this, '<%- comment_id -%>'); return false">
242 <strong>Resolve TODO</strong>
242 <strong>Resolve TODO</strong>
243 </a>
243 </a>
244 </div>
244 </div>
245 <% } %>
245 <% } %>
246
246
247 </div>
247 </div>
248
248
249 </script>
249 </script>
250
250
251 <script id="ejs_commentHelpHovercard" type="text/template" class="ejsTemplate">
251 <script id="ejs_commentHelpHovercard" type="text/template" class="ejsTemplate">
252
252
253 <div>
253 <div>
254 Use <strong>@username</strong> mention syntax to send direct notification to this RhodeCode user.<br/>
254 Use <strong>@username</strong> mention syntax to send direct notification to this RhodeCode user.<br/>
255 Typing / starts autocomplete for certain action, e.g set review status, or comment type. <br/>
255 Typing / starts autocomplete for certain action, e.g set review status, or comment type. <br/>
256 <br/>
256 <br/>
257 Use <strong>Cmd/ctrl+enter</strong> to submit comment, or <strong>Shift+Cmd/ctrl+enter</strong> to submit a draft.<br/>
257 Use <strong>Cmd/ctrl+enter</strong> to submit comment, or <strong>Shift+Cmd/ctrl+enter</strong> to submit a draft.<br/>
258 <br/>
258 <br/>
259 <strong>Draft comments</strong> are private to the author, and trigger no notification to others.<br/>
259 <strong>Draft comments</strong> are private to the author, and trigger no notification to others.<br/>
260 They are permanent until deleted, or converted to regular comments.<br/>
260 They are permanent until deleted, or converted to regular comments.<br/>
261 <br/>
261 <br/>
262 <br/>
262 <br/>
263 </div>
263 </div>
264
264
265 </script>
265 </script>
266
266
267
267
268
268
269 ##// END OF EJS Templates
269 ##// END OF EJS Templates
270 </div>
270 </div>
271
271
272
272
273 <script>
273 <script>
274 // registers the templates into global cache
274 // registers the templates into global cache
275 registerTemplates();
275 registerTemplates();
276 </script>
276 </script>
277
277
278 </%text>
278 </%text>
General Comments 0
You need to be logged in to leave comments. Login now