##// END OF EJS Templates
comments-history: Fix Utf8 to 64 encoding
wuboo -
r4416:8708d303 default
parent child Browse files
Show More
@@ -1,1263 +1,1263 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 var linkifyComments = function(comments) {
28 var linkifyComments = function(comments) {
29 var firstCommentId = null;
29 var firstCommentId = null;
30 if (comments) {
30 if (comments) {
31 firstCommentId = $(comments[0]).data('comment-id');
31 firstCommentId = $(comments[0]).data('comment-id');
32 }
32 }
33
33
34 if (firstCommentId){
34 if (firstCommentId){
35 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
35 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
36 }
36 }
37 };
37 };
38
38
39 var bindToggleButtons = function() {
39 var bindToggleButtons = function() {
40 $('.comment-toggle').on('click', function() {
40 $('.comment-toggle').on('click', function() {
41 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
41 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
42 });
42 });
43 };
43 };
44
44
45
45
46
46
47 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
47 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
48 failHandler = failHandler || function() {};
48 failHandler = failHandler || function() {};
49 postData = toQueryString(postData);
49 postData = toQueryString(postData);
50 var request = $.ajax({
50 var request = $.ajax({
51 url: url,
51 url: url,
52 type: 'POST',
52 type: 'POST',
53 data: postData,
53 data: postData,
54 headers: {'X-PARTIAL-XHR': true}
54 headers: {'X-PARTIAL-XHR': true}
55 })
55 })
56 .done(function (data) {
56 .done(function (data) {
57 successHandler(data);
57 successHandler(data);
58 })
58 })
59 .fail(function (data, textStatus, errorThrown) {
59 .fail(function (data, textStatus, errorThrown) {
60 failHandler(data, textStatus, errorThrown)
60 failHandler(data, textStatus, errorThrown)
61 });
61 });
62 return request;
62 return request;
63 };
63 };
64
64
65
65
66
66
67
67
68 /* Comment form for main and inline comments */
68 /* Comment form for main and inline comments */
69 (function(mod) {
69 (function(mod) {
70
70
71 if (typeof exports == "object" && typeof module == "object") {
71 if (typeof exports == "object" && typeof module == "object") {
72 // CommonJS
72 // CommonJS
73 module.exports = mod();
73 module.exports = mod();
74 }
74 }
75 else {
75 else {
76 // Plain browser env
76 // Plain browser env
77 (this || window).CommentForm = mod();
77 (this || window).CommentForm = mod();
78 }
78 }
79
79
80 })(function() {
80 })(function() {
81 "use strict";
81 "use strict";
82
82
83 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id) {
83 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id) {
84
84
85 if (!(this instanceof CommentForm)) {
85 if (!(this instanceof CommentForm)) {
86 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id);
86 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id);
87 }
87 }
88
88
89 // bind the element instance to our Form
89 // bind the element instance to our Form
90 $(formElement).get(0).CommentForm = this;
90 $(formElement).get(0).CommentForm = this;
91
91
92 this.withLineNo = function(selector) {
92 this.withLineNo = function(selector) {
93 var lineNo = this.lineNo;
93 var lineNo = this.lineNo;
94 if (lineNo === undefined) {
94 if (lineNo === undefined) {
95 return selector
95 return selector
96 } else {
96 } else {
97 return selector + '_' + lineNo;
97 return selector + '_' + lineNo;
98 }
98 }
99 };
99 };
100
100
101 this.commitId = commitId;
101 this.commitId = commitId;
102 this.pullRequestId = pullRequestId;
102 this.pullRequestId = pullRequestId;
103 this.lineNo = lineNo;
103 this.lineNo = lineNo;
104 this.initAutocompleteActions = initAutocompleteActions;
104 this.initAutocompleteActions = initAutocompleteActions;
105
105
106 this.previewButton = this.withLineNo('#preview-btn');
106 this.previewButton = this.withLineNo('#preview-btn');
107 this.previewContainer = this.withLineNo('#preview-container');
107 this.previewContainer = this.withLineNo('#preview-container');
108
108
109 this.previewBoxSelector = this.withLineNo('#preview-box');
109 this.previewBoxSelector = this.withLineNo('#preview-box');
110
110
111 this.editButton = this.withLineNo('#edit-btn');
111 this.editButton = this.withLineNo('#edit-btn');
112 this.editContainer = this.withLineNo('#edit-container');
112 this.editContainer = this.withLineNo('#edit-container');
113 this.cancelButton = this.withLineNo('#cancel-btn');
113 this.cancelButton = this.withLineNo('#cancel-btn');
114 this.commentType = this.withLineNo('#comment_type');
114 this.commentType = this.withLineNo('#comment_type');
115
115
116 this.resolvesId = null;
116 this.resolvesId = null;
117 this.resolvesActionId = null;
117 this.resolvesActionId = null;
118
118
119 this.closesPr = '#close_pull_request';
119 this.closesPr = '#close_pull_request';
120
120
121 this.cmBox = this.withLineNo('#text');
121 this.cmBox = this.withLineNo('#text');
122 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
122 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
123
123
124 this.statusChange = this.withLineNo('#change_status');
124 this.statusChange = this.withLineNo('#change_status');
125
125
126 this.submitForm = formElement;
126 this.submitForm = formElement;
127 this.submitButton = $(this.submitForm).find('input[type="submit"]');
127 this.submitButton = $(this.submitForm).find('input[type="submit"]');
128 this.submitButtonText = this.submitButton.val();
128 this.submitButtonText = this.submitButton.val();
129
129
130
130
131 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
131 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
132 {'repo_name': templateContext.repo_name,
132 {'repo_name': templateContext.repo_name,
133 'commit_id': templateContext.commit_data.commit_id});
133 'commit_id': templateContext.commit_data.commit_id});
134
134
135 if (edit){
135 if (edit){
136 this.submitButtonText = _gettext('Updated Comment');
136 this.submitButtonText = _gettext('Updated Comment');
137 $(this.commentType).prop('disabled', true);
137 $(this.commentType).prop('disabled', true);
138 $(this.commentType).addClass('disabled');
138 $(this.commentType).addClass('disabled');
139 var editInfo =
139 var editInfo =
140 '';
140 '';
141 $(editInfo).insertBefore($(this.editButton).parent());
141 $(editInfo).insertBefore($(this.editButton).parent());
142 }
142 }
143
143
144 if (resolvesCommentId){
144 if (resolvesCommentId){
145 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
145 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
146 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
146 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
147 $(this.commentType).prop('disabled', true);
147 $(this.commentType).prop('disabled', true);
148 $(this.commentType).addClass('disabled');
148 $(this.commentType).addClass('disabled');
149
149
150 // disable select
150 // disable select
151 setTimeout(function() {
151 setTimeout(function() {
152 $(self.statusChange).select2('readonly', true);
152 $(self.statusChange).select2('readonly', true);
153 }, 10);
153 }, 10);
154
154
155 var resolvedInfo = (
155 var resolvedInfo = (
156 '<li class="resolve-action">' +
156 '<li class="resolve-action">' +
157 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
157 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
158 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
158 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
159 '</li>'
159 '</li>'
160 ).format(resolvesCommentId, _gettext('resolve comment'));
160 ).format(resolvesCommentId, _gettext('resolve comment'));
161 $(resolvedInfo).insertAfter($(this.commentType).parent());
161 $(resolvedInfo).insertAfter($(this.commentType).parent());
162 }
162 }
163
163
164 // based on commitId, or pullRequestId decide where do we submit
164 // based on commitId, or pullRequestId decide where do we submit
165 // out data
165 // out data
166 if (this.commitId){
166 if (this.commitId){
167 var pyurl = 'repo_commit_comment_create';
167 var pyurl = 'repo_commit_comment_create';
168 if(edit){
168 if(edit){
169 pyurl = 'repo_commit_comment_edit';
169 pyurl = 'repo_commit_comment_edit';
170 }
170 }
171 this.submitUrl = pyroutes.url(pyurl,
171 this.submitUrl = pyroutes.url(pyurl,
172 {'repo_name': templateContext.repo_name,
172 {'repo_name': templateContext.repo_name,
173 'commit_id': this.commitId,
173 'commit_id': this.commitId,
174 'comment_id': comment_id});
174 'comment_id': comment_id});
175 this.selfUrl = pyroutes.url('repo_commit',
175 this.selfUrl = pyroutes.url('repo_commit',
176 {'repo_name': templateContext.repo_name,
176 {'repo_name': templateContext.repo_name,
177 'commit_id': this.commitId});
177 'commit_id': this.commitId});
178
178
179 } else if (this.pullRequestId) {
179 } else if (this.pullRequestId) {
180 var pyurl = 'pullrequest_comment_create';
180 var pyurl = 'pullrequest_comment_create';
181 if(edit){
181 if(edit){
182 pyurl = 'pullrequest_comment_edit';
182 pyurl = 'pullrequest_comment_edit';
183 }
183 }
184 this.submitUrl = pyroutes.url(pyurl,
184 this.submitUrl = pyroutes.url(pyurl,
185 {'repo_name': templateContext.repo_name,
185 {'repo_name': templateContext.repo_name,
186 'pull_request_id': this.pullRequestId,
186 'pull_request_id': this.pullRequestId,
187 'comment_id': comment_id});
187 'comment_id': comment_id});
188 this.selfUrl = pyroutes.url('pullrequest_show',
188 this.selfUrl = pyroutes.url('pullrequest_show',
189 {'repo_name': templateContext.repo_name,
189 {'repo_name': templateContext.repo_name,
190 'pull_request_id': this.pullRequestId});
190 'pull_request_id': this.pullRequestId});
191
191
192 } else {
192 } else {
193 throw new Error(
193 throw new Error(
194 'CommentForm requires pullRequestId, or commitId to be specified.')
194 'CommentForm requires pullRequestId, or commitId to be specified.')
195 }
195 }
196
196
197 // FUNCTIONS and helpers
197 // FUNCTIONS and helpers
198 var self = this;
198 var self = this;
199
199
200 this.isInline = function(){
200 this.isInline = function(){
201 return this.lineNo && this.lineNo != 'general';
201 return this.lineNo && this.lineNo != 'general';
202 };
202 };
203
203
204 this.getCmInstance = function(){
204 this.getCmInstance = function(){
205 return this.cm
205 return this.cm
206 };
206 };
207
207
208 this.setPlaceholder = function(placeholder) {
208 this.setPlaceholder = function(placeholder) {
209 var cm = this.getCmInstance();
209 var cm = this.getCmInstance();
210 if (cm){
210 if (cm){
211 cm.setOption('placeholder', placeholder);
211 cm.setOption('placeholder', placeholder);
212 }
212 }
213 };
213 };
214
214
215 this.getCommentStatus = function() {
215 this.getCommentStatus = function() {
216 return $(this.submitForm).find(this.statusChange).val();
216 return $(this.submitForm).find(this.statusChange).val();
217 };
217 };
218 this.getCommentType = function() {
218 this.getCommentType = function() {
219 return $(this.submitForm).find(this.commentType).val();
219 return $(this.submitForm).find(this.commentType).val();
220 };
220 };
221
221
222 this.getResolvesId = function() {
222 this.getResolvesId = function() {
223 return $(this.submitForm).find(this.resolvesId).val() || null;
223 return $(this.submitForm).find(this.resolvesId).val() || null;
224 };
224 };
225
225
226 this.getClosePr = function() {
226 this.getClosePr = function() {
227 return $(this.submitForm).find(this.closesPr).val() || null;
227 return $(this.submitForm).find(this.closesPr).val() || null;
228 };
228 };
229
229
230 this.markCommentResolved = function(resolvedCommentId){
230 this.markCommentResolved = function(resolvedCommentId){
231 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
231 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
232 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
232 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
233 };
233 };
234
234
235 this.isAllowedToSubmit = function() {
235 this.isAllowedToSubmit = function() {
236 return !$(this.submitButton).prop('disabled');
236 return !$(this.submitButton).prop('disabled');
237 };
237 };
238
238
239 this.initStatusChangeSelector = function(){
239 this.initStatusChangeSelector = function(){
240 var formatChangeStatus = function(state, escapeMarkup) {
240 var formatChangeStatus = function(state, escapeMarkup) {
241 var originalOption = state.element;
241 var originalOption = state.element;
242 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
242 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
243 return tmpl
243 return tmpl
244 };
244 };
245 var formatResult = function(result, container, query, escapeMarkup) {
245 var formatResult = function(result, container, query, escapeMarkup) {
246 return formatChangeStatus(result, escapeMarkup);
246 return formatChangeStatus(result, escapeMarkup);
247 };
247 };
248
248
249 var formatSelection = function(data, container, escapeMarkup) {
249 var formatSelection = function(data, container, escapeMarkup) {
250 return formatChangeStatus(data, escapeMarkup);
250 return formatChangeStatus(data, escapeMarkup);
251 };
251 };
252
252
253 $(this.submitForm).find(this.statusChange).select2({
253 $(this.submitForm).find(this.statusChange).select2({
254 placeholder: _gettext('Status Review'),
254 placeholder: _gettext('Status Review'),
255 formatResult: formatResult,
255 formatResult: formatResult,
256 formatSelection: formatSelection,
256 formatSelection: formatSelection,
257 containerCssClass: "drop-menu status_box_menu",
257 containerCssClass: "drop-menu status_box_menu",
258 dropdownCssClass: "drop-menu-dropdown",
258 dropdownCssClass: "drop-menu-dropdown",
259 dropdownAutoWidth: true,
259 dropdownAutoWidth: true,
260 minimumResultsForSearch: -1
260 minimumResultsForSearch: -1
261 });
261 });
262 $(this.submitForm).find(this.statusChange).on('change', function() {
262 $(this.submitForm).find(this.statusChange).on('change', function() {
263 var status = self.getCommentStatus();
263 var status = self.getCommentStatus();
264
264
265 if (status && !self.isInline()) {
265 if (status && !self.isInline()) {
266 $(self.submitButton).prop('disabled', false);
266 $(self.submitButton).prop('disabled', false);
267 }
267 }
268
268
269 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
269 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
270 self.setPlaceholder(placeholderText)
270 self.setPlaceholder(placeholderText)
271 })
271 })
272 };
272 };
273
273
274 // reset the comment form into it's original state
274 // reset the comment form into it's original state
275 this.resetCommentFormState = function(content) {
275 this.resetCommentFormState = function(content) {
276 content = content || '';
276 content = content || '';
277
277
278 $(this.editContainer).show();
278 $(this.editContainer).show();
279 $(this.editButton).parent().addClass('active');
279 $(this.editButton).parent().addClass('active');
280
280
281 $(this.previewContainer).hide();
281 $(this.previewContainer).hide();
282 $(this.previewButton).parent().removeClass('active');
282 $(this.previewButton).parent().removeClass('active');
283
283
284 this.setActionButtonsDisabled(true);
284 this.setActionButtonsDisabled(true);
285 self.cm.setValue(content);
285 self.cm.setValue(content);
286 self.cm.setOption("readOnly", false);
286 self.cm.setOption("readOnly", false);
287
287
288 if (this.resolvesId) {
288 if (this.resolvesId) {
289 // destroy the resolve action
289 // destroy the resolve action
290 $(this.resolvesId).parent().remove();
290 $(this.resolvesId).parent().remove();
291 }
291 }
292 // reset closingPR flag
292 // reset closingPR flag
293 $('.close-pr-input').remove();
293 $('.close-pr-input').remove();
294
294
295 $(this.statusChange).select2('readonly', false);
295 $(this.statusChange).select2('readonly', false);
296 };
296 };
297
297
298 this.globalSubmitSuccessCallback = function(){
298 this.globalSubmitSuccessCallback = function(){
299 // default behaviour is to call GLOBAL hook, if it's registered.
299 // default behaviour is to call GLOBAL hook, if it's registered.
300 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
300 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
301 commentFormGlobalSubmitSuccessCallback();
301 commentFormGlobalSubmitSuccessCallback();
302 }
302 }
303 };
303 };
304
304
305 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
305 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
306 return _submitAjaxPOST(url, postData, successHandler, failHandler);
306 return _submitAjaxPOST(url, postData, successHandler, failHandler);
307 };
307 };
308
308
309 // overwrite a submitHandler, we need to do it for inline comments
309 // overwrite a submitHandler, we need to do it for inline comments
310 this.setHandleFormSubmit = function(callback) {
310 this.setHandleFormSubmit = function(callback) {
311 this.handleFormSubmit = callback;
311 this.handleFormSubmit = callback;
312 };
312 };
313
313
314 // overwrite a submitSuccessHandler
314 // overwrite a submitSuccessHandler
315 this.setGlobalSubmitSuccessCallback = function(callback) {
315 this.setGlobalSubmitSuccessCallback = function(callback) {
316 this.globalSubmitSuccessCallback = callback;
316 this.globalSubmitSuccessCallback = callback;
317 };
317 };
318
318
319 // default handler for for submit for main comments
319 // default handler for for submit for main comments
320 this.handleFormSubmit = function() {
320 this.handleFormSubmit = function() {
321 var text = self.cm.getValue();
321 var text = self.cm.getValue();
322 var status = self.getCommentStatus();
322 var status = self.getCommentStatus();
323 var commentType = self.getCommentType();
323 var commentType = self.getCommentType();
324 var resolvesCommentId = self.getResolvesId();
324 var resolvesCommentId = self.getResolvesId();
325 var closePullRequest = self.getClosePr();
325 var closePullRequest = self.getClosePr();
326
326
327 if (text === "" && !status) {
327 if (text === "" && !status) {
328 return;
328 return;
329 }
329 }
330
330
331 var excludeCancelBtn = false;
331 var excludeCancelBtn = false;
332 var submitEvent = true;
332 var submitEvent = true;
333 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
333 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
334 self.cm.setOption("readOnly", true);
334 self.cm.setOption("readOnly", true);
335
335
336 var postData = {
336 var postData = {
337 'text': text,
337 'text': text,
338 'changeset_status': status,
338 'changeset_status': status,
339 'comment_type': commentType,
339 'comment_type': commentType,
340 'csrf_token': CSRF_TOKEN
340 'csrf_token': CSRF_TOKEN
341 };
341 };
342
342
343 if (resolvesCommentId) {
343 if (resolvesCommentId) {
344 postData['resolves_comment_id'] = resolvesCommentId;
344 postData['resolves_comment_id'] = resolvesCommentId;
345 }
345 }
346
346
347 if (closePullRequest) {
347 if (closePullRequest) {
348 postData['close_pull_request'] = true;
348 postData['close_pull_request'] = true;
349 }
349 }
350
350
351 var submitSuccessCallback = function(o) {
351 var submitSuccessCallback = function(o) {
352 // reload page if we change status for single commit.
352 // reload page if we change status for single commit.
353 if (status && self.commitId) {
353 if (status && self.commitId) {
354 location.reload(true);
354 location.reload(true);
355 } else {
355 } else {
356 $('#injected_page_comments').append(o.rendered_text);
356 $('#injected_page_comments').append(o.rendered_text);
357 self.resetCommentFormState();
357 self.resetCommentFormState();
358 timeagoActivate();
358 timeagoActivate();
359 tooltipActivate();
359 tooltipActivate();
360
360
361 // mark visually which comment was resolved
361 // mark visually which comment was resolved
362 if (resolvesCommentId) {
362 if (resolvesCommentId) {
363 self.markCommentResolved(resolvesCommentId);
363 self.markCommentResolved(resolvesCommentId);
364 }
364 }
365 }
365 }
366
366
367 // run global callback on submit
367 // run global callback on submit
368 self.globalSubmitSuccessCallback();
368 self.globalSubmitSuccessCallback();
369
369
370 };
370 };
371 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
371 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
372 var prefix = "Error while submitting comment.\n"
372 var prefix = "Error while submitting comment.\n"
373 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
373 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
374 ajaxErrorSwal(message);
374 ajaxErrorSwal(message);
375 self.resetCommentFormState(text);
375 self.resetCommentFormState(text);
376 };
376 };
377 self.submitAjaxPOST(
377 self.submitAjaxPOST(
378 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
378 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
379 };
379 };
380
380
381 this.previewSuccessCallback = function(o) {
381 this.previewSuccessCallback = function(o) {
382 $(self.previewBoxSelector).html(o);
382 $(self.previewBoxSelector).html(o);
383 $(self.previewBoxSelector).removeClass('unloaded');
383 $(self.previewBoxSelector).removeClass('unloaded');
384
384
385 // swap buttons, making preview active
385 // swap buttons, making preview active
386 $(self.previewButton).parent().addClass('active');
386 $(self.previewButton).parent().addClass('active');
387 $(self.editButton).parent().removeClass('active');
387 $(self.editButton).parent().removeClass('active');
388
388
389 // unlock buttons
389 // unlock buttons
390 self.setActionButtonsDisabled(false);
390 self.setActionButtonsDisabled(false);
391 };
391 };
392
392
393 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
393 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
394 excludeCancelBtn = excludeCancelBtn || false;
394 excludeCancelBtn = excludeCancelBtn || false;
395 submitEvent = submitEvent || false;
395 submitEvent = submitEvent || false;
396
396
397 $(this.editButton).prop('disabled', state);
397 $(this.editButton).prop('disabled', state);
398 $(this.previewButton).prop('disabled', state);
398 $(this.previewButton).prop('disabled', state);
399
399
400 if (!excludeCancelBtn) {
400 if (!excludeCancelBtn) {
401 $(this.cancelButton).prop('disabled', state);
401 $(this.cancelButton).prop('disabled', state);
402 }
402 }
403
403
404 var submitState = state;
404 var submitState = state;
405 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
405 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
406 // if the value of commit review status is set, we allow
406 // if the value of commit review status is set, we allow
407 // submit button, but only on Main form, isInline means inline
407 // submit button, but only on Main form, isInline means inline
408 submitState = false
408 submitState = false
409 }
409 }
410
410
411 $(this.submitButton).prop('disabled', submitState);
411 $(this.submitButton).prop('disabled', submitState);
412 if (submitEvent) {
412 if (submitEvent) {
413 $(this.submitButton).val(_gettext('Submitting...'));
413 $(this.submitButton).val(_gettext('Submitting...'));
414 } else {
414 } else {
415 $(this.submitButton).val(this.submitButtonText);
415 $(this.submitButton).val(this.submitButtonText);
416 }
416 }
417
417
418 };
418 };
419
419
420 // lock preview/edit/submit buttons on load, but exclude cancel button
420 // lock preview/edit/submit buttons on load, but exclude cancel button
421 var excludeCancelBtn = true;
421 var excludeCancelBtn = true;
422 this.setActionButtonsDisabled(true, excludeCancelBtn);
422 this.setActionButtonsDisabled(true, excludeCancelBtn);
423
423
424 // anonymous users don't have access to initialized CM instance
424 // anonymous users don't have access to initialized CM instance
425 if (this.cm !== undefined){
425 if (this.cm !== undefined){
426 this.cm.on('change', function(cMirror) {
426 this.cm.on('change', function(cMirror) {
427 if (cMirror.getValue() === "") {
427 if (cMirror.getValue() === "") {
428 self.setActionButtonsDisabled(true, excludeCancelBtn)
428 self.setActionButtonsDisabled(true, excludeCancelBtn)
429 } else {
429 } else {
430 self.setActionButtonsDisabled(false, excludeCancelBtn)
430 self.setActionButtonsDisabled(false, excludeCancelBtn)
431 }
431 }
432 });
432 });
433 }
433 }
434
434
435 $(this.editButton).on('click', function(e) {
435 $(this.editButton).on('click', function(e) {
436 e.preventDefault();
436 e.preventDefault();
437
437
438 $(self.previewButton).parent().removeClass('active');
438 $(self.previewButton).parent().removeClass('active');
439 $(self.previewContainer).hide();
439 $(self.previewContainer).hide();
440
440
441 $(self.editButton).parent().addClass('active');
441 $(self.editButton).parent().addClass('active');
442 $(self.editContainer).show();
442 $(self.editContainer).show();
443
443
444 });
444 });
445
445
446 $(this.previewButton).on('click', function(e) {
446 $(this.previewButton).on('click', function(e) {
447 e.preventDefault();
447 e.preventDefault();
448 var text = self.cm.getValue();
448 var text = self.cm.getValue();
449
449
450 if (text === "") {
450 if (text === "") {
451 return;
451 return;
452 }
452 }
453
453
454 var postData = {
454 var postData = {
455 'text': text,
455 'text': text,
456 'renderer': templateContext.visual.default_renderer,
456 'renderer': templateContext.visual.default_renderer,
457 'csrf_token': CSRF_TOKEN
457 'csrf_token': CSRF_TOKEN
458 };
458 };
459
459
460 // lock ALL buttons on preview
460 // lock ALL buttons on preview
461 self.setActionButtonsDisabled(true);
461 self.setActionButtonsDisabled(true);
462
462
463 $(self.previewBoxSelector).addClass('unloaded');
463 $(self.previewBoxSelector).addClass('unloaded');
464 $(self.previewBoxSelector).html(_gettext('Loading ...'));
464 $(self.previewBoxSelector).html(_gettext('Loading ...'));
465
465
466 $(self.editContainer).hide();
466 $(self.editContainer).hide();
467 $(self.previewContainer).show();
467 $(self.previewContainer).show();
468
468
469 // by default we reset state of comment preserving the text
469 // by default we reset state of comment preserving the text
470 var previewFailCallback = function(jqXHR, textStatus, errorThrown) {
470 var previewFailCallback = function(jqXHR, textStatus, errorThrown) {
471 var prefix = "Error while preview of comment.\n"
471 var prefix = "Error while preview of comment.\n"
472 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
472 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
473 ajaxErrorSwal(message);
473 ajaxErrorSwal(message);
474
474
475 self.resetCommentFormState(text)
475 self.resetCommentFormState(text)
476 };
476 };
477 self.submitAjaxPOST(
477 self.submitAjaxPOST(
478 self.previewUrl, postData, self.previewSuccessCallback,
478 self.previewUrl, postData, self.previewSuccessCallback,
479 previewFailCallback);
479 previewFailCallback);
480
480
481 $(self.previewButton).parent().addClass('active');
481 $(self.previewButton).parent().addClass('active');
482 $(self.editButton).parent().removeClass('active');
482 $(self.editButton).parent().removeClass('active');
483 });
483 });
484
484
485 $(this.submitForm).submit(function(e) {
485 $(this.submitForm).submit(function(e) {
486 e.preventDefault();
486 e.preventDefault();
487 var allowedToSubmit = self.isAllowedToSubmit();
487 var allowedToSubmit = self.isAllowedToSubmit();
488 if (!allowedToSubmit){
488 if (!allowedToSubmit){
489 return false;
489 return false;
490 }
490 }
491 self.handleFormSubmit();
491 self.handleFormSubmit();
492 });
492 });
493
493
494 }
494 }
495
495
496 return CommentForm;
496 return CommentForm;
497 });
497 });
498
498
499 /* selector for comment versions */
499 /* selector for comment versions */
500 var initVersionSelector = function(selector, initialData) {
500 var initVersionSelector = function(selector, initialData) {
501
501
502 var formatResult = function(result, container, query, escapeMarkup) {
502 var formatResult = function(result, container, query, escapeMarkup) {
503
503
504 return renderTemplate('commentVersion', {
504 return renderTemplate('commentVersion', {
505 show_disabled: true,
505 show_disabled: true,
506 version: result.comment_version,
506 version: result.comment_version,
507 user_name: result.comment_author_username,
507 user_name: result.comment_author_username,
508 gravatar_url: result.comment_author_gravatar,
508 gravatar_url: result.comment_author_gravatar,
509 size: 16,
509 size: 16,
510 timeago_component: result.comment_created_on,
510 timeago_component: result.comment_created_on,
511 })
511 })
512 };
512 };
513
513
514 $(selector).select2({
514 $(selector).select2({
515 placeholder: "Edited",
515 placeholder: "Edited",
516 containerCssClass: "drop-menu-comment-history",
516 containerCssClass: "drop-menu-comment-history",
517 dropdownCssClass: "drop-menu-dropdown",
517 dropdownCssClass: "drop-menu-dropdown",
518 dropdownAutoWidth: true,
518 dropdownAutoWidth: true,
519 minimumResultsForSearch: -1,
519 minimumResultsForSearch: -1,
520 data: initialData,
520 data: initialData,
521 formatResult: formatResult,
521 formatResult: formatResult,
522 });
522 });
523
523
524 $(selector).on('select2-selecting', function (e) {
524 $(selector).on('select2-selecting', function (e) {
525 // hide the mast as we later do preventDefault()
525 // hide the mast as we later do preventDefault()
526 $("#select2-drop-mask").click();
526 $("#select2-drop-mask").click();
527 e.preventDefault();
527 e.preventDefault();
528 e.choice.action();
528 e.choice.action();
529 });
529 });
530
530
531 $(selector).on("select2-open", function() {
531 $(selector).on("select2-open", function() {
532 timeagoActivate();
532 timeagoActivate();
533 });
533 });
534 };
534 };
535
535
536 /* comments controller */
536 /* comments controller */
537 var CommentsController = function() {
537 var CommentsController = function() {
538 var mainComment = '#text';
538 var mainComment = '#text';
539 var self = this;
539 var self = this;
540
540
541 this.cancelComment = function (node) {
541 this.cancelComment = function (node) {
542 var $node = $(node);
542 var $node = $(node);
543 var edit = $(this).attr('edit');
543 var edit = $(this).attr('edit');
544 if (edit) {
544 if (edit) {
545 var $general_comments = null;
545 var $general_comments = null;
546 var $inline_comments = $node.closest('div.inline-comments');
546 var $inline_comments = $node.closest('div.inline-comments');
547 if (!$inline_comments.length) {
547 if (!$inline_comments.length) {
548 $general_comments = $('#comments');
548 $general_comments = $('#comments');
549 var $comment = $general_comments.parent().find('div.comment:hidden');
549 var $comment = $general_comments.parent().find('div.comment:hidden');
550 // show hidden general comment form
550 // show hidden general comment form
551 $('#cb-comment-general-form-placeholder').show();
551 $('#cb-comment-general-form-placeholder').show();
552 } else {
552 } else {
553 var $comment = $inline_comments.find('div.comment:hidden');
553 var $comment = $inline_comments.find('div.comment:hidden');
554 }
554 }
555 $comment.show();
555 $comment.show();
556 }
556 }
557 $node.closest('.comment-inline-form').remove();
557 $node.closest('.comment-inline-form').remove();
558 return false;
558 return false;
559 };
559 };
560
560
561 this.showVersion = function (comment_id, comment_history_id) {
561 this.showVersion = function (comment_id, comment_history_id) {
562
562
563 var historyViewUrl = pyroutes.url(
563 var historyViewUrl = pyroutes.url(
564 'repo_commit_comment_history_view',
564 'repo_commit_comment_history_view',
565 {
565 {
566 'repo_name': templateContext.repo_name,
566 'repo_name': templateContext.repo_name,
567 'commit_id': comment_id,
567 'commit_id': comment_id,
568 'comment_history_id': comment_history_id,
568 'comment_history_id': comment_history_id,
569 }
569 }
570 );
570 );
571 successRenderCommit = function (data) {
571 successRenderCommit = function (data) {
572 SwalNoAnimation.fire({
572 SwalNoAnimation.fire({
573 html: data,
573 html: data,
574 title: '',
574 title: '',
575 });
575 });
576 };
576 };
577 failRenderCommit = function () {
577 failRenderCommit = function () {
578 SwalNoAnimation.fire({
578 SwalNoAnimation.fire({
579 html: 'Error while loading comment history',
579 html: 'Error while loading comment history',
580 title: '',
580 title: '',
581 });
581 });
582 };
582 };
583 _submitAjaxPOST(
583 _submitAjaxPOST(
584 historyViewUrl, {'csrf_token': CSRF_TOKEN},
584 historyViewUrl, {'csrf_token': CSRF_TOKEN},
585 successRenderCommit,
585 successRenderCommit,
586 failRenderCommit
586 failRenderCommit
587 );
587 );
588 };
588 };
589
589
590 this.getLineNumber = function(node) {
590 this.getLineNumber = function(node) {
591 var $node = $(node);
591 var $node = $(node);
592 var lineNo = $node.closest('td').attr('data-line-no');
592 var lineNo = $node.closest('td').attr('data-line-no');
593 if (lineNo === undefined && $node.data('commentInline')){
593 if (lineNo === undefined && $node.data('commentInline')){
594 lineNo = $node.data('commentLineNo')
594 lineNo = $node.data('commentLineNo')
595 }
595 }
596
596
597 return lineNo
597 return lineNo
598 };
598 };
599
599
600 this.scrollToComment = function(node, offset, outdated) {
600 this.scrollToComment = function(node, offset, outdated) {
601 if (offset === undefined) {
601 if (offset === undefined) {
602 offset = 0;
602 offset = 0;
603 }
603 }
604 var outdated = outdated || false;
604 var outdated = outdated || false;
605 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
605 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
606
606
607 if (!node) {
607 if (!node) {
608 node = $('.comment-selected');
608 node = $('.comment-selected');
609 if (!node.length) {
609 if (!node.length) {
610 node = $('comment-current')
610 node = $('comment-current')
611 }
611 }
612 }
612 }
613
613
614 $wrapper = $(node).closest('div.comment');
614 $wrapper = $(node).closest('div.comment');
615
615
616 // show hidden comment when referenced.
616 // show hidden comment when referenced.
617 if (!$wrapper.is(':visible')){
617 if (!$wrapper.is(':visible')){
618 $wrapper.show();
618 $wrapper.show();
619 }
619 }
620
620
621 $comment = $(node).closest(klass);
621 $comment = $(node).closest(klass);
622 $comments = $(klass);
622 $comments = $(klass);
623
623
624 $('.comment-selected').removeClass('comment-selected');
624 $('.comment-selected').removeClass('comment-selected');
625
625
626 var nextIdx = $(klass).index($comment) + offset;
626 var nextIdx = $(klass).index($comment) + offset;
627 if (nextIdx >= $comments.length) {
627 if (nextIdx >= $comments.length) {
628 nextIdx = 0;
628 nextIdx = 0;
629 }
629 }
630 var $next = $(klass).eq(nextIdx);
630 var $next = $(klass).eq(nextIdx);
631
631
632 var $cb = $next.closest('.cb');
632 var $cb = $next.closest('.cb');
633 $cb.removeClass('cb-collapsed');
633 $cb.removeClass('cb-collapsed');
634
634
635 var $filediffCollapseState = $cb.closest('.filediff').prev();
635 var $filediffCollapseState = $cb.closest('.filediff').prev();
636 $filediffCollapseState.prop('checked', false);
636 $filediffCollapseState.prop('checked', false);
637 $next.addClass('comment-selected');
637 $next.addClass('comment-selected');
638 scrollToElement($next);
638 scrollToElement($next);
639 return false;
639 return false;
640 };
640 };
641
641
642 this.nextComment = function(node) {
642 this.nextComment = function(node) {
643 return self.scrollToComment(node, 1);
643 return self.scrollToComment(node, 1);
644 };
644 };
645
645
646 this.prevComment = function(node) {
646 this.prevComment = function(node) {
647 return self.scrollToComment(node, -1);
647 return self.scrollToComment(node, -1);
648 };
648 };
649
649
650 this.nextOutdatedComment = function(node) {
650 this.nextOutdatedComment = function(node) {
651 return self.scrollToComment(node, 1, true);
651 return self.scrollToComment(node, 1, true);
652 };
652 };
653
653
654 this.prevOutdatedComment = function(node) {
654 this.prevOutdatedComment = function(node) {
655 return self.scrollToComment(node, -1, true);
655 return self.scrollToComment(node, -1, true);
656 };
656 };
657
657
658 this._deleteComment = function(node) {
658 this._deleteComment = function(node) {
659 var $node = $(node);
659 var $node = $(node);
660 var $td = $node.closest('td');
660 var $td = $node.closest('td');
661 var $comment = $node.closest('.comment');
661 var $comment = $node.closest('.comment');
662 var comment_id = $comment.attr('data-comment-id');
662 var comment_id = $comment.attr('data-comment-id');
663 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
663 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
664 var postData = {
664 var postData = {
665 'csrf_token': CSRF_TOKEN
665 'csrf_token': CSRF_TOKEN
666 };
666 };
667
667
668 $comment.addClass('comment-deleting');
668 $comment.addClass('comment-deleting');
669 $comment.hide('fast');
669 $comment.hide('fast');
670
670
671 var success = function(response) {
671 var success = function(response) {
672 $comment.remove();
672 $comment.remove();
673 return false;
673 return false;
674 };
674 };
675 var failure = function(jqXHR, textStatus, errorThrown) {
675 var failure = function(jqXHR, textStatus, errorThrown) {
676 var prefix = "Error while deleting this comment.\n"
676 var prefix = "Error while deleting this comment.\n"
677 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
677 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
678 ajaxErrorSwal(message);
678 ajaxErrorSwal(message);
679
679
680 $comment.show('fast');
680 $comment.show('fast');
681 $comment.removeClass('comment-deleting');
681 $comment.removeClass('comment-deleting');
682 return false;
682 return false;
683 };
683 };
684 ajaxPOST(url, postData, success, failure);
684 ajaxPOST(url, postData, success, failure);
685 }
685 }
686
686
687 this.deleteComment = function(node) {
687 this.deleteComment = function(node) {
688 var $comment = $(node).closest('.comment');
688 var $comment = $(node).closest('.comment');
689 var comment_id = $comment.attr('data-comment-id');
689 var comment_id = $comment.attr('data-comment-id');
690
690
691 SwalNoAnimation.fire({
691 SwalNoAnimation.fire({
692 title: 'Delete this comment?',
692 title: 'Delete this comment?',
693 icon: 'warning',
693 icon: 'warning',
694 showCancelButton: true,
694 showCancelButton: true,
695 confirmButtonText: _gettext('Yes, delete comment #{0}!').format(comment_id),
695 confirmButtonText: _gettext('Yes, delete comment #{0}!').format(comment_id),
696
696
697 }).then(function(result) {
697 }).then(function(result) {
698 if (result.value) {
698 if (result.value) {
699 self._deleteComment(node);
699 self._deleteComment(node);
700 }
700 }
701 })
701 })
702 };
702 };
703
703
704 this.toggleWideMode = function (node) {
704 this.toggleWideMode = function (node) {
705 if ($('#content').hasClass('wrapper')) {
705 if ($('#content').hasClass('wrapper')) {
706 $('#content').removeClass("wrapper");
706 $('#content').removeClass("wrapper");
707 $('#content').addClass("wide-mode-wrapper");
707 $('#content').addClass("wide-mode-wrapper");
708 $(node).addClass('btn-success');
708 $(node).addClass('btn-success');
709 return true
709 return true
710 } else {
710 } else {
711 $('#content').removeClass("wide-mode-wrapper");
711 $('#content').removeClass("wide-mode-wrapper");
712 $('#content').addClass("wrapper");
712 $('#content').addClass("wrapper");
713 $(node).removeClass('btn-success');
713 $(node).removeClass('btn-success');
714 return false
714 return false
715 }
715 }
716
716
717 };
717 };
718
718
719 this.toggleComments = function(node, show) {
719 this.toggleComments = function(node, show) {
720 var $filediff = $(node).closest('.filediff');
720 var $filediff = $(node).closest('.filediff');
721 if (show === true) {
721 if (show === true) {
722 $filediff.removeClass('hide-comments');
722 $filediff.removeClass('hide-comments');
723 } else if (show === false) {
723 } else if (show === false) {
724 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
724 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
725 $filediff.addClass('hide-comments');
725 $filediff.addClass('hide-comments');
726 } else {
726 } else {
727 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
727 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
728 $filediff.toggleClass('hide-comments');
728 $filediff.toggleClass('hide-comments');
729 }
729 }
730 return false;
730 return false;
731 };
731 };
732
732
733 this.toggleLineComments = function(node) {
733 this.toggleLineComments = function(node) {
734 self.toggleComments(node, true);
734 self.toggleComments(node, true);
735 var $node = $(node);
735 var $node = $(node);
736 // mark outdated comments as visible before the toggle;
736 // mark outdated comments as visible before the toggle;
737 $(node.closest('tr')).find('.comment-outdated').show();
737 $(node.closest('tr')).find('.comment-outdated').show();
738 $node.closest('tr').toggleClass('hide-line-comments');
738 $node.closest('tr').toggleClass('hide-line-comments');
739 };
739 };
740
740
741 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
741 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
742 var pullRequestId = templateContext.pull_request_data.pull_request_id;
742 var pullRequestId = templateContext.pull_request_data.pull_request_id;
743 var commitId = templateContext.commit_data.commit_id;
743 var commitId = templateContext.commit_data.commit_id;
744
744
745 var commentForm = new CommentForm(
745 var commentForm = new CommentForm(
746 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId, edit, comment_id);
746 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId, edit, comment_id);
747 var cm = commentForm.getCmInstance();
747 var cm = commentForm.getCmInstance();
748
748
749 if (resolvesCommentId){
749 if (resolvesCommentId){
750 var placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
750 var placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
751 }
751 }
752
752
753 setTimeout(function() {
753 setTimeout(function() {
754 // callbacks
754 // callbacks
755 if (cm !== undefined) {
755 if (cm !== undefined) {
756 commentForm.setPlaceholder(placeholderText);
756 commentForm.setPlaceholder(placeholderText);
757 if (commentForm.isInline()) {
757 if (commentForm.isInline()) {
758 cm.focus();
758 cm.focus();
759 cm.refresh();
759 cm.refresh();
760 }
760 }
761 }
761 }
762 }, 10);
762 }, 10);
763
763
764 // trigger scrolldown to the resolve comment, since it might be away
764 // trigger scrolldown to the resolve comment, since it might be away
765 // from the clicked
765 // from the clicked
766 if (resolvesCommentId){
766 if (resolvesCommentId){
767 var actionNode = $(commentForm.resolvesActionId).offset();
767 var actionNode = $(commentForm.resolvesActionId).offset();
768
768
769 setTimeout(function() {
769 setTimeout(function() {
770 if (actionNode) {
770 if (actionNode) {
771 $('body, html').animate({scrollTop: actionNode.top}, 10);
771 $('body, html').animate({scrollTop: actionNode.top}, 10);
772 }
772 }
773 }, 100);
773 }, 100);
774 }
774 }
775
775
776 // add dropzone support
776 // add dropzone support
777 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
777 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
778 var renderer = templateContext.visual.default_renderer;
778 var renderer = templateContext.visual.default_renderer;
779 if (renderer == 'rst') {
779 if (renderer == 'rst') {
780 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
780 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
781 if (isRendered){
781 if (isRendered){
782 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
782 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
783 }
783 }
784 } else if (renderer == 'markdown') {
784 } else if (renderer == 'markdown') {
785 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
785 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
786 if (isRendered){
786 if (isRendered){
787 attachmentUrl = '!' + attachmentUrl;
787 attachmentUrl = '!' + attachmentUrl;
788 }
788 }
789 } else {
789 } else {
790 var attachmentUrl = '{}'.format(attachmentStoreUrl);
790 var attachmentUrl = '{}'.format(attachmentStoreUrl);
791 }
791 }
792 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
792 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
793
793
794 return false;
794 return false;
795 };
795 };
796
796
797 //see: https://www.dropzonejs.com/#configuration
797 //see: https://www.dropzonejs.com/#configuration
798 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
798 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
799 {'repo_name': templateContext.repo_name,
799 {'repo_name': templateContext.repo_name,
800 'commit_id': templateContext.commit_data.commit_id})
800 'commit_id': templateContext.commit_data.commit_id})
801
801
802 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
802 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
803 if (previewTmpl !== undefined){
803 if (previewTmpl !== undefined){
804 var selectLink = $(formElement).find('.pick-attachment').get(0);
804 var selectLink = $(formElement).find('.pick-attachment').get(0);
805 $(formElement).find('.comment-attachment-uploader').dropzone({
805 $(formElement).find('.comment-attachment-uploader').dropzone({
806 url: storeUrl,
806 url: storeUrl,
807 headers: {"X-CSRF-Token": CSRF_TOKEN},
807 headers: {"X-CSRF-Token": CSRF_TOKEN},
808 paramName: function () {
808 paramName: function () {
809 return "attachment"
809 return "attachment"
810 }, // The name that will be used to transfer the file
810 }, // The name that will be used to transfer the file
811 clickable: selectLink,
811 clickable: selectLink,
812 parallelUploads: 1,
812 parallelUploads: 1,
813 maxFiles: 10,
813 maxFiles: 10,
814 maxFilesize: templateContext.attachment_store.max_file_size_mb,
814 maxFilesize: templateContext.attachment_store.max_file_size_mb,
815 uploadMultiple: false,
815 uploadMultiple: false,
816 autoProcessQueue: true, // if false queue will not be processed automatically.
816 autoProcessQueue: true, // if false queue will not be processed automatically.
817 createImageThumbnails: false,
817 createImageThumbnails: false,
818 previewTemplate: previewTmpl.innerHTML,
818 previewTemplate: previewTmpl.innerHTML,
819
819
820 accept: function (file, done) {
820 accept: function (file, done) {
821 done();
821 done();
822 },
822 },
823 init: function () {
823 init: function () {
824
824
825 this.on("sending", function (file, xhr, formData) {
825 this.on("sending", function (file, xhr, formData) {
826 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
826 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
827 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
827 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
828 });
828 });
829
829
830 this.on("success", function (file, response) {
830 this.on("success", function (file, response) {
831 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
831 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
832 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
832 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
833
833
834 var isRendered = false;
834 var isRendered = false;
835 var ext = file.name.split('.').pop();
835 var ext = file.name.split('.').pop();
836 var imageExts = templateContext.attachment_store.image_ext;
836 var imageExts = templateContext.attachment_store.image_ext;
837 if (imageExts.indexOf(ext) !== -1){
837 if (imageExts.indexOf(ext) !== -1){
838 isRendered = true;
838 isRendered = true;
839 }
839 }
840
840
841 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
841 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
842 });
842 });
843
843
844 this.on("error", function (file, errorMessage, xhr) {
844 this.on("error", function (file, errorMessage, xhr) {
845 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
845 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
846
846
847 var error = null;
847 var error = null;
848
848
849 if (xhr !== undefined){
849 if (xhr !== undefined){
850 var httpStatus = xhr.status + " " + xhr.statusText;
850 var httpStatus = xhr.status + " " + xhr.statusText;
851 if (xhr !== undefined && xhr.status >= 500) {
851 if (xhr !== undefined && xhr.status >= 500) {
852 error = httpStatus;
852 error = httpStatus;
853 }
853 }
854 }
854 }
855
855
856 if (error === null) {
856 if (error === null) {
857 error = errorMessage.error || errorMessage || httpStatus;
857 error = errorMessage.error || errorMessage || httpStatus;
858 }
858 }
859 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
859 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
860
860
861 });
861 });
862 }
862 }
863 });
863 });
864 }
864 }
865 return commentForm;
865 return commentForm;
866 };
866 };
867
867
868 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
868 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
869
869
870 var tmpl = $('#cb-comment-general-form-template').html();
870 var tmpl = $('#cb-comment-general-form-template').html();
871 tmpl = tmpl.format(null, 'general');
871 tmpl = tmpl.format(null, 'general');
872 var $form = $(tmpl);
872 var $form = $(tmpl);
873
873
874 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
874 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
875 var curForm = $formPlaceholder.find('form');
875 var curForm = $formPlaceholder.find('form');
876 if (curForm){
876 if (curForm){
877 curForm.remove();
877 curForm.remove();
878 }
878 }
879 $formPlaceholder.append($form);
879 $formPlaceholder.append($form);
880
880
881 var _form = $($form[0]);
881 var _form = $($form[0]);
882 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
882 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
883 var edit = false;
883 var edit = false;
884 var comment_id = null;
884 var comment_id = null;
885 var commentForm = this.createCommentForm(
885 var commentForm = this.createCommentForm(
886 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId, edit, comment_id);
886 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId, edit, comment_id);
887 commentForm.initStatusChangeSelector();
887 commentForm.initStatusChangeSelector();
888
888
889 return commentForm;
889 return commentForm;
890 };
890 };
891
891
892 this.editComment = function(node) {
892 this.editComment = function(node) {
893 var $node = $(node);
893 var $node = $(node);
894 var $comment = $(node).closest('.comment');
894 var $comment = $(node).closest('.comment');
895 var comment_id = $comment.attr('data-comment-id');
895 var comment_id = $comment.attr('data-comment-id');
896 var $form = null
896 var $form = null
897
897
898 var $comments = $node.closest('div.inline-comments');
898 var $comments = $node.closest('div.inline-comments');
899 var $general_comments = null;
899 var $general_comments = null;
900 var lineno = null;
900 var lineno = null;
901
901
902 if($comments.length){
902 if($comments.length){
903 // inline comments setup
903 // inline comments setup
904 $form = $comments.find('.comment-inline-form');
904 $form = $comments.find('.comment-inline-form');
905 lineno = self.getLineNumber(node)
905 lineno = self.getLineNumber(node)
906 }
906 }
907 else{
907 else{
908 // general comments setup
908 // general comments setup
909 $comments = $('#comments');
909 $comments = $('#comments');
910 $form = $comments.find('.comment-inline-form');
910 $form = $comments.find('.comment-inline-form');
911 lineno = $comment[0].id
911 lineno = $comment[0].id
912 $('#cb-comment-general-form-placeholder').hide();
912 $('#cb-comment-general-form-placeholder').hide();
913 }
913 }
914
914
915 this.edit = true;
915 this.edit = true;
916
916
917 if (!$form.length) {
917 if (!$form.length) {
918
918
919 var $filediff = $node.closest('.filediff');
919 var $filediff = $node.closest('.filediff');
920 $filediff.removeClass('hide-comments');
920 $filediff.removeClass('hide-comments');
921 var f_path = $filediff.attr('data-f-path');
921 var f_path = $filediff.attr('data-f-path');
922
922
923 // create a new HTML from template
923 // create a new HTML from template
924
924
925 var tmpl = $('#cb-comment-inline-form-template').html();
925 var tmpl = $('#cb-comment-inline-form-template').html();
926 tmpl = tmpl.format(escapeHtml(f_path), lineno);
926 tmpl = tmpl.format(escapeHtml(f_path), lineno);
927 $form = $(tmpl);
927 $form = $(tmpl);
928 $comment.after($form)
928 $comment.after($form)
929
929
930 var _form = $($form[0]).find('form');
930 var _form = $($form[0]).find('form');
931 var autocompleteActions = ['as_note',];
931 var autocompleteActions = ['as_note',];
932 var commentForm = this.createCommentForm(
932 var commentForm = this.createCommentForm(
933 _form, lineno, '', autocompleteActions, resolvesCommentId,
933 _form, lineno, '', autocompleteActions, resolvesCommentId,
934 this.edit, comment_id);
934 this.edit, comment_id);
935 var old_comment_text_binary = $comment.attr('data-comment-text');
935 var old_comment_text_binary = $comment.attr('data-comment-text');
936 var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
936 var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
937 commentForm.cm.setValue(old_comment_text);
937 commentForm.cm.setValue(old_comment_text);
938 $comment.hide();
938 $comment.hide();
939
939
940 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
940 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
941 form: _form,
941 form: _form,
942 parent: $comments,
942 parent: $comments,
943 lineno: lineno,
943 lineno: lineno,
944 f_path: f_path}
944 f_path: f_path}
945 );
945 );
946
946
947 // set a CUSTOM submit handler for inline comments.
947 // set a CUSTOM submit handler for inline comments.
948 commentForm.setHandleFormSubmit(function(o) {
948 commentForm.setHandleFormSubmit(function(o) {
949 var text = commentForm.cm.getValue();
949 var text = commentForm.cm.getValue();
950 var commentType = commentForm.getCommentType();
950 var commentType = commentForm.getCommentType();
951
951
952 if (text === "") {
952 if (text === "") {
953 return;
953 return;
954 }
954 }
955
955
956 if (old_comment_text == text) {
956 if (old_comment_text == text) {
957 SwalNoAnimation.fire({
957 SwalNoAnimation.fire({
958 title: 'Unable to edit comment',
958 title: 'Unable to edit comment',
959 html: _gettext('Comment body was not changed.'),
959 html: _gettext('Comment body was not changed.'),
960 });
960 });
961 return;
961 return;
962 }
962 }
963 var excludeCancelBtn = false;
963 var excludeCancelBtn = false;
964 var submitEvent = true;
964 var submitEvent = true;
965 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
965 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
966 commentForm.cm.setOption("readOnly", true);
966 commentForm.cm.setOption("readOnly", true);
967
967
968 // Read last version known
968 // Read last version known
969 var versionSelector = $('#comment_versions_{0}'.format(comment_id));
969 var versionSelector = $('#comment_versions_{0}'.format(comment_id));
970 var version = versionSelector.data('lastVersion');
970 var version = versionSelector.data('lastVersion');
971
971
972 if (!version) {
972 if (!version) {
973 version = 0;
973 version = 0;
974 }
974 }
975
975
976 var postData = {
976 var postData = {
977 'text': text,
977 'text': text,
978 'f_path': f_path,
978 'f_path': f_path,
979 'line': lineno,
979 'line': lineno,
980 'comment_type': commentType,
980 'comment_type': commentType,
981 'version': version,
981 'version': version,
982 'csrf_token': CSRF_TOKEN
982 'csrf_token': CSRF_TOKEN
983 };
983 };
984
984
985 var submitSuccessCallback = function(json_data) {
985 var submitSuccessCallback = function(json_data) {
986 $form.remove();
986 $form.remove();
987 $comment.show();
987 $comment.show();
988 var postData = {
988 var postData = {
989 'text': text,
989 'text': text,
990 'renderer': $comment.attr('data-comment-renderer'),
990 'renderer': $comment.attr('data-comment-renderer'),
991 'csrf_token': CSRF_TOKEN
991 'csrf_token': CSRF_TOKEN
992 };
992 };
993
993
994 /* Inject new edited version selector */
994 /* Inject new edited version selector */
995 var updateCommentVersionDropDown = function () {
995 var updateCommentVersionDropDown = function () {
996 var versionSelectId = '#comment_versions_'+comment_id;
996 var versionSelectId = '#comment_versions_'+comment_id;
997 var preLoadVersionData = [
997 var preLoadVersionData = [
998 {
998 {
999 id: json_data['comment_version'],
999 id: json_data['comment_version'],
1000 text: "v{0}".format(json_data['comment_version']),
1000 text: "v{0}".format(json_data['comment_version']),
1001 action: function () {
1001 action: function () {
1002 Rhodecode.comments.showVersion(
1002 Rhodecode.comments.showVersion(
1003 json_data['comment_id'],
1003 json_data['comment_id'],
1004 json_data['comment_history_id']
1004 json_data['comment_history_id']
1005 )
1005 )
1006 },
1006 },
1007 comment_version: json_data['comment_version'],
1007 comment_version: json_data['comment_version'],
1008 comment_author_username: json_data['comment_author_username'],
1008 comment_author_username: json_data['comment_author_username'],
1009 comment_author_gravatar: json_data['comment_author_gravatar'],
1009 comment_author_gravatar: json_data['comment_author_gravatar'],
1010 comment_created_on: json_data['comment_created_on'],
1010 comment_created_on: json_data['comment_created_on'],
1011 },
1011 },
1012 ]
1012 ]
1013
1013
1014
1014
1015 if ($(versionSelectId).data('select2')) {
1015 if ($(versionSelectId).data('select2')) {
1016 var oldData = $(versionSelectId).data('select2').opts.data.results;
1016 var oldData = $(versionSelectId).data('select2').opts.data.results;
1017 $(versionSelectId).select2("destroy");
1017 $(versionSelectId).select2("destroy");
1018 preLoadVersionData = oldData.concat(preLoadVersionData)
1018 preLoadVersionData = oldData.concat(preLoadVersionData)
1019 }
1019 }
1020
1020
1021 initVersionSelector(versionSelectId, {results: preLoadVersionData});
1021 initVersionSelector(versionSelectId, {results: preLoadVersionData});
1022
1022
1023 $comment.attr('data-comment-text', btoa(text));
1023 $comment.attr('data-comment-text', utf8ToB64(text));
1024
1024
1025 var versionSelector = $('#comment_versions_'+comment_id);
1025 var versionSelector = $('#comment_versions_'+comment_id);
1026
1026
1027 // set lastVersion so we know our last edit version
1027 // set lastVersion so we know our last edit version
1028 versionSelector.data('lastVersion', json_data['comment_version'])
1028 versionSelector.data('lastVersion', json_data['comment_version'])
1029 versionSelector.parent().show();
1029 versionSelector.parent().show();
1030 }
1030 }
1031 updateCommentVersionDropDown();
1031 updateCommentVersionDropDown();
1032
1032
1033 // by default we reset state of comment preserving the text
1033 // by default we reset state of comment preserving the text
1034 var failRenderCommit = function(jqXHR, textStatus, errorThrown) {
1034 var failRenderCommit = function(jqXHR, textStatus, errorThrown) {
1035 var prefix = "Error while editing this comment.\n"
1035 var prefix = "Error while editing this comment.\n"
1036 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1036 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1037 ajaxErrorSwal(message);
1037 ajaxErrorSwal(message);
1038 };
1038 };
1039
1039
1040 var successRenderCommit = function(o){
1040 var successRenderCommit = function(o){
1041 $comment.show();
1041 $comment.show();
1042 $comment[0].lastElementChild.innerHTML = o;
1042 $comment[0].lastElementChild.innerHTML = o;
1043 };
1043 };
1044
1044
1045 var previewUrl = pyroutes.url(
1045 var previewUrl = pyroutes.url(
1046 'repo_commit_comment_preview',
1046 'repo_commit_comment_preview',
1047 {'repo_name': templateContext.repo_name,
1047 {'repo_name': templateContext.repo_name,
1048 'commit_id': templateContext.commit_data.commit_id});
1048 'commit_id': templateContext.commit_data.commit_id});
1049
1049
1050 _submitAjaxPOST(
1050 _submitAjaxPOST(
1051 previewUrl, postData, successRenderCommit,
1051 previewUrl, postData, successRenderCommit,
1052 failRenderCommit
1052 failRenderCommit
1053 );
1053 );
1054
1054
1055 try {
1055 try {
1056 var html = json_data.rendered_text;
1056 var html = json_data.rendered_text;
1057 var lineno = json_data.line_no;
1057 var lineno = json_data.line_no;
1058 var target_id = json_data.target_id;
1058 var target_id = json_data.target_id;
1059
1059
1060 $comments.find('.cb-comment-add-button').before(html);
1060 $comments.find('.cb-comment-add-button').before(html);
1061
1061
1062 // run global callback on submit
1062 // run global callback on submit
1063 commentForm.globalSubmitSuccessCallback();
1063 commentForm.globalSubmitSuccessCallback();
1064
1064
1065 } catch (e) {
1065 } catch (e) {
1066 console.error(e);
1066 console.error(e);
1067 }
1067 }
1068
1068
1069 // re trigger the linkification of next/prev navigation
1069 // re trigger the linkification of next/prev navigation
1070 linkifyComments($('.inline-comment-injected'));
1070 linkifyComments($('.inline-comment-injected'));
1071 timeagoActivate();
1071 timeagoActivate();
1072 tooltipActivate();
1072 tooltipActivate();
1073
1073
1074 if (window.updateSticky !== undefined) {
1074 if (window.updateSticky !== undefined) {
1075 // potentially our comments change the active window size, so we
1075 // potentially our comments change the active window size, so we
1076 // notify sticky elements
1076 // notify sticky elements
1077 updateSticky()
1077 updateSticky()
1078 }
1078 }
1079
1079
1080 commentForm.setActionButtonsDisabled(false);
1080 commentForm.setActionButtonsDisabled(false);
1081
1081
1082 };
1082 };
1083 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1083 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1084 var prefix = "Error while editing comment.\n"
1084 var prefix = "Error while editing comment.\n"
1085 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1085 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1086 if (jqXHR.status == 409){
1086 if (jqXHR.status == 409){
1087 message = 'This comment was probably changed somewhere else. Please reload the content of this comment.'
1087 message = 'This comment was probably changed somewhere else. Please reload the content of this comment.'
1088 ajaxErrorSwal(message, 'Comment version mismatch.');
1088 ajaxErrorSwal(message, 'Comment version mismatch.');
1089 } else {
1089 } else {
1090 ajaxErrorSwal(message);
1090 ajaxErrorSwal(message);
1091 }
1091 }
1092
1092
1093 commentForm.resetCommentFormState(text)
1093 commentForm.resetCommentFormState(text)
1094 };
1094 };
1095 commentForm.submitAjaxPOST(
1095 commentForm.submitAjaxPOST(
1096 commentForm.submitUrl, postData,
1096 commentForm.submitUrl, postData,
1097 submitSuccessCallback,
1097 submitSuccessCallback,
1098 submitFailCallback);
1098 submitFailCallback);
1099 });
1099 });
1100 }
1100 }
1101
1101
1102 $form.addClass('comment-inline-form-open');
1102 $form.addClass('comment-inline-form-open');
1103 };
1103 };
1104
1104
1105 this.createComment = function(node, resolutionComment) {
1105 this.createComment = function(node, resolutionComment) {
1106 var resolvesCommentId = resolutionComment || null;
1106 var resolvesCommentId = resolutionComment || null;
1107 var $node = $(node);
1107 var $node = $(node);
1108 var $td = $node.closest('td');
1108 var $td = $node.closest('td');
1109 var $form = $td.find('.comment-inline-form');
1109 var $form = $td.find('.comment-inline-form');
1110 this.edit = false;
1110 this.edit = false;
1111
1111
1112 if (!$form.length) {
1112 if (!$form.length) {
1113
1113
1114 var $filediff = $node.closest('.filediff');
1114 var $filediff = $node.closest('.filediff');
1115 $filediff.removeClass('hide-comments');
1115 $filediff.removeClass('hide-comments');
1116 var f_path = $filediff.attr('data-f-path');
1116 var f_path = $filediff.attr('data-f-path');
1117 var lineno = self.getLineNumber(node);
1117 var lineno = self.getLineNumber(node);
1118 // create a new HTML from template
1118 // create a new HTML from template
1119 var tmpl = $('#cb-comment-inline-form-template').html();
1119 var tmpl = $('#cb-comment-inline-form-template').html();
1120 tmpl = tmpl.format(escapeHtml(f_path), lineno);
1120 tmpl = tmpl.format(escapeHtml(f_path), lineno);
1121 $form = $(tmpl);
1121 $form = $(tmpl);
1122
1122
1123 var $comments = $td.find('.inline-comments');
1123 var $comments = $td.find('.inline-comments');
1124 if (!$comments.length) {
1124 if (!$comments.length) {
1125 $comments = $(
1125 $comments = $(
1126 $('#cb-comments-inline-container-template').html());
1126 $('#cb-comments-inline-container-template').html());
1127 $td.append($comments);
1127 $td.append($comments);
1128 }
1128 }
1129
1129
1130 $td.find('.cb-comment-add-button').before($form);
1130 $td.find('.cb-comment-add-button').before($form);
1131
1131
1132 var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
1132 var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
1133 var _form = $($form[0]).find('form');
1133 var _form = $($form[0]).find('form');
1134 var autocompleteActions = ['as_note', 'as_todo'];
1134 var autocompleteActions = ['as_note', 'as_todo'];
1135 var comment_id=null;
1135 var comment_id=null;
1136 var commentForm = this.createCommentForm(
1136 var commentForm = this.createCommentForm(
1137 _form, lineno, placeholderText, autocompleteActions, resolvesCommentId, this.edit, comment_id);
1137 _form, lineno, placeholderText, autocompleteActions, resolvesCommentId, this.edit, comment_id);
1138
1138
1139 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
1139 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
1140 form: _form,
1140 form: _form,
1141 parent: $td[0],
1141 parent: $td[0],
1142 lineno: lineno,
1142 lineno: lineno,
1143 f_path: f_path}
1143 f_path: f_path}
1144 );
1144 );
1145
1145
1146 // set a CUSTOM submit handler for inline comments.
1146 // set a CUSTOM submit handler for inline comments.
1147 commentForm.setHandleFormSubmit(function(o) {
1147 commentForm.setHandleFormSubmit(function(o) {
1148 var text = commentForm.cm.getValue();
1148 var text = commentForm.cm.getValue();
1149 var commentType = commentForm.getCommentType();
1149 var commentType = commentForm.getCommentType();
1150 var resolvesCommentId = commentForm.getResolvesId();
1150 var resolvesCommentId = commentForm.getResolvesId();
1151
1151
1152 if (text === "") {
1152 if (text === "") {
1153 return;
1153 return;
1154 }
1154 }
1155
1155
1156 if (lineno === undefined) {
1156 if (lineno === undefined) {
1157 alert('missing line !');
1157 alert('missing line !');
1158 return;
1158 return;
1159 }
1159 }
1160 if (f_path === undefined) {
1160 if (f_path === undefined) {
1161 alert('missing file path !');
1161 alert('missing file path !');
1162 return;
1162 return;
1163 }
1163 }
1164
1164
1165 var excludeCancelBtn = false;
1165 var excludeCancelBtn = false;
1166 var submitEvent = true;
1166 var submitEvent = true;
1167 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1167 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1168 commentForm.cm.setOption("readOnly", true);
1168 commentForm.cm.setOption("readOnly", true);
1169 var postData = {
1169 var postData = {
1170 'text': text,
1170 'text': text,
1171 'f_path': f_path,
1171 'f_path': f_path,
1172 'line': lineno,
1172 'line': lineno,
1173 'comment_type': commentType,
1173 'comment_type': commentType,
1174 'csrf_token': CSRF_TOKEN
1174 'csrf_token': CSRF_TOKEN
1175 };
1175 };
1176 if (resolvesCommentId){
1176 if (resolvesCommentId){
1177 postData['resolves_comment_id'] = resolvesCommentId;
1177 postData['resolves_comment_id'] = resolvesCommentId;
1178 }
1178 }
1179
1179
1180 var submitSuccessCallback = function(json_data) {
1180 var submitSuccessCallback = function(json_data) {
1181 $form.remove();
1181 $form.remove();
1182 try {
1182 try {
1183 var html = json_data.rendered_text;
1183 var html = json_data.rendered_text;
1184 var lineno = json_data.line_no;
1184 var lineno = json_data.line_no;
1185 var target_id = json_data.target_id;
1185 var target_id = json_data.target_id;
1186
1186
1187 $comments.find('.cb-comment-add-button').before(html);
1187 $comments.find('.cb-comment-add-button').before(html);
1188
1188
1189 //mark visually which comment was resolved
1189 //mark visually which comment was resolved
1190 if (resolvesCommentId) {
1190 if (resolvesCommentId) {
1191 commentForm.markCommentResolved(resolvesCommentId);
1191 commentForm.markCommentResolved(resolvesCommentId);
1192 }
1192 }
1193
1193
1194 // run global callback on submit
1194 // run global callback on submit
1195 commentForm.globalSubmitSuccessCallback();
1195 commentForm.globalSubmitSuccessCallback();
1196
1196
1197 } catch (e) {
1197 } catch (e) {
1198 console.error(e);
1198 console.error(e);
1199 }
1199 }
1200
1200
1201 // re trigger the linkification of next/prev navigation
1201 // re trigger the linkification of next/prev navigation
1202 linkifyComments($('.inline-comment-injected'));
1202 linkifyComments($('.inline-comment-injected'));
1203 timeagoActivate();
1203 timeagoActivate();
1204 tooltipActivate();
1204 tooltipActivate();
1205
1205
1206 if (window.updateSticky !== undefined) {
1206 if (window.updateSticky !== undefined) {
1207 // potentially our comments change the active window size, so we
1207 // potentially our comments change the active window size, so we
1208 // notify sticky elements
1208 // notify sticky elements
1209 updateSticky()
1209 updateSticky()
1210 }
1210 }
1211
1211
1212 commentForm.setActionButtonsDisabled(false);
1212 commentForm.setActionButtonsDisabled(false);
1213
1213
1214 };
1214 };
1215 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1215 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1216 var prefix = "Error while submitting comment.\n"
1216 var prefix = "Error while submitting comment.\n"
1217 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1217 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1218 ajaxErrorSwal(message);
1218 ajaxErrorSwal(message);
1219 commentForm.resetCommentFormState(text)
1219 commentForm.resetCommentFormState(text)
1220 };
1220 };
1221 commentForm.submitAjaxPOST(
1221 commentForm.submitAjaxPOST(
1222 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1222 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1223 });
1223 });
1224 }
1224 }
1225
1225
1226 $form.addClass('comment-inline-form-open');
1226 $form.addClass('comment-inline-form-open');
1227 };
1227 };
1228
1228
1229 this.createResolutionComment = function(commentId){
1229 this.createResolutionComment = function(commentId){
1230 // hide the trigger text
1230 // hide the trigger text
1231 $('#resolve-comment-{0}'.format(commentId)).hide();
1231 $('#resolve-comment-{0}'.format(commentId)).hide();
1232
1232
1233 var comment = $('#comment-'+commentId);
1233 var comment = $('#comment-'+commentId);
1234 var commentData = comment.data();
1234 var commentData = comment.data();
1235 if (commentData.commentInline) {
1235 if (commentData.commentInline) {
1236 this.createComment(comment, commentId)
1236 this.createComment(comment, commentId)
1237 } else {
1237 } else {
1238 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
1238 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
1239 }
1239 }
1240
1240
1241 return false;
1241 return false;
1242 };
1242 };
1243
1243
1244 this.submitResolution = function(commentId){
1244 this.submitResolution = function(commentId){
1245 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
1245 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
1246 var commentForm = form.get(0).CommentForm;
1246 var commentForm = form.get(0).CommentForm;
1247
1247
1248 var cm = commentForm.getCmInstance();
1248 var cm = commentForm.getCmInstance();
1249 var renderer = templateContext.visual.default_renderer;
1249 var renderer = templateContext.visual.default_renderer;
1250 if (renderer == 'rst'){
1250 if (renderer == 'rst'){
1251 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
1251 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
1252 } else if (renderer == 'markdown') {
1252 } else if (renderer == 'markdown') {
1253 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
1253 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
1254 } else {
1254 } else {
1255 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
1255 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
1256 }
1256 }
1257
1257
1258 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
1258 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
1259 form.submit();
1259 form.submit();
1260 return false;
1260 return false;
1261 };
1261 };
1262
1262
1263 };
1263 };
@@ -1,190 +1,194 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 /**
19 /**
20 * INJECT .format function into String
20 * INJECT .format function into String
21 * Usage: "My name is {0} {1}".format("Johny","Bravo")
21 * Usage: "My name is {0} {1}".format("Johny","Bravo")
22 * Return "My name is Johny Bravo"
22 * Return "My name is Johny Bravo"
23 * Inspired by https://gist.github.com/1049426
23 * Inspired by https://gist.github.com/1049426
24 */
24 */
25 String.prototype.format = function() {
25 String.prototype.format = function() {
26
26
27 function format() {
27 function format() {
28 var str = this;
28 var str = this;
29 var len = arguments.length+1;
29 var len = arguments.length+1;
30 var safe = undefined;
30 var safe = undefined;
31 var arg = undefined;
31 var arg = undefined;
32
32
33 // For each {0} {1} {n...} replace with the argument in that position. If
33 // For each {0} {1} {n...} replace with the argument in that position. If
34 // the argument is an object or an array it will be stringified to JSON.
34 // the argument is an object or an array it will be stringified to JSON.
35 for (var i=0; i < len; arg = arguments[i++]) {
35 for (var i=0; i < len; arg = arguments[i++]) {
36 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
36 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
37 str = str.replace(new RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
37 str = str.replace(new RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
38 }
38 }
39 return str;
39 return str;
40 }
40 }
41
41
42 // Save a reference of what may already exist under the property native.
42 // Save a reference of what may already exist under the property native.
43 // Allows for doing something like: if("".format.native) { /* use native */ }
43 // Allows for doing something like: if("".format.native) { /* use native */ }
44 format.native = String.prototype.format;
44 format.native = String.prototype.format;
45
45
46 // Replace the prototype property
46 // Replace the prototype property
47 return format;
47 return format;
48 }();
48 }();
49
49
50 String.prototype.strip = function(char) {
50 String.prototype.strip = function(char) {
51 if(char === undefined){
51 if(char === undefined){
52 char = '\\s';
52 char = '\\s';
53 }
53 }
54 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
54 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
55 };
55 };
56
56
57 String.prototype.lstrip = function(char) {
57 String.prototype.lstrip = function(char) {
58 if(char === undefined){
58 if(char === undefined){
59 char = '\\s';
59 char = '\\s';
60 }
60 }
61 return this.replace(new RegExp('^'+char+'+'),'');
61 return this.replace(new RegExp('^'+char+'+'),'');
62 };
62 };
63
63
64 String.prototype.rstrip = function(char) {
64 String.prototype.rstrip = function(char) {
65 if(char === undefined){
65 if(char === undefined){
66 char = '\\s';
66 char = '\\s';
67 }
67 }
68 return this.replace(new RegExp(''+char+'+$'),'');
68 return this.replace(new RegExp(''+char+'+$'),'');
69 };
69 };
70
70
71 String.prototype.capitalizeFirstLetter = function() {
71 String.prototype.capitalizeFirstLetter = function() {
72 return this.charAt(0).toUpperCase() + this.slice(1);
72 return this.charAt(0).toUpperCase() + this.slice(1);
73 };
73 };
74
74
75
75
76 String.prototype.truncateAfter = function(chars, suffix) {
76 String.prototype.truncateAfter = function(chars, suffix) {
77 var suffix = suffix || '';
77 var suffix = suffix || '';
78 if (this.length > chars) {
78 if (this.length > chars) {
79 return this.substr(0, chars) + suffix;
79 return this.substr(0, chars) + suffix;
80 } else {
80 } else {
81 return this;
81 return this;
82 }
82 }
83 };
83 };
84
84
85
85
86 /**
86 /**
87 * Splits remainder
87 * Splits remainder
88 *
88 *
89 * @param input
89 * @param input
90 */
90 */
91 function splitDelimitedHash(input){
91 function splitDelimitedHash(input){
92 var splitIx = input.indexOf('/?/');
92 var splitIx = input.indexOf('/?/');
93 if (splitIx !== -1){
93 if (splitIx !== -1){
94 var loc = input.slice(0, splitIx);
94 var loc = input.slice(0, splitIx);
95 var remainder = input.slice(splitIx + 2);
95 var remainder = input.slice(splitIx + 2);
96 }
96 }
97 else{
97 else{
98 var loc = input;
98 var loc = input;
99 var remainder = null;
99 var remainder = null;
100 }
100 }
101 //fixes for some urls generated incorrectly
101 //fixes for some urls generated incorrectly
102 var result = loc.match('#+(.*)');
102 var result = loc.match('#+(.*)');
103 if (result !== null){
103 if (result !== null){
104 loc = '#' + result[1];
104 loc = '#' + result[1];
105 }
105 }
106 return {loc:loc, remainder: remainder}
106 return {loc:loc, remainder: remainder}
107 }
107 }
108
108
109 /**
109 /**
110 * Escape html characters in string
110 * Escape html characters in string
111 */
111 */
112 var entityMap = {
112 var entityMap = {
113 "&": "&amp;",
113 "&": "&amp;",
114 "<": "&lt;",
114 "<": "&lt;",
115 ">": "&gt;",
115 ">": "&gt;",
116 '"': '&quot;',
116 '"': '&quot;',
117 "'": '&#39;',
117 "'": '&#39;',
118 "/": '&#x2F;'
118 "/": '&#x2F;'
119 };
119 };
120
120
121 function escapeHtml(string) {
121 function escapeHtml(string) {
122 return String(string).replace(/[&<>"'\/]/g, function (s) {
122 return String(string).replace(/[&<>"'\/]/g, function (s) {
123 return entityMap[s];
123 return entityMap[s];
124 });
124 });
125 }
125 }
126
126
127 /** encode/decode html special chars**/
127 /** encode/decode html special chars**/
128 var htmlEnDeCode = (function() {
128 var htmlEnDeCode = (function() {
129 var charToEntityRegex,
129 var charToEntityRegex,
130 entityToCharRegex,
130 entityToCharRegex,
131 charToEntity,
131 charToEntity,
132 entityToChar;
132 entityToChar;
133
133
134 function resetCharacterEntities() {
134 function resetCharacterEntities() {
135 charToEntity = {};
135 charToEntity = {};
136 entityToChar = {};
136 entityToChar = {};
137 // add the default set
137 // add the default set
138 addCharacterEntities({
138 addCharacterEntities({
139 '&amp;' : '&',
139 '&amp;' : '&',
140 '&gt;' : '>',
140 '&gt;' : '>',
141 '&lt;' : '<',
141 '&lt;' : '<',
142 '&quot;' : '"',
142 '&quot;' : '"',
143 '&#39;' : "'"
143 '&#39;' : "'"
144 });
144 });
145 }
145 }
146
146
147 function addCharacterEntities(newEntities) {
147 function addCharacterEntities(newEntities) {
148 var charKeys = [],
148 var charKeys = [],
149 entityKeys = [],
149 entityKeys = [],
150 key, echar;
150 key, echar;
151 for (key in newEntities) {
151 for (key in newEntities) {
152 echar = newEntities[key];
152 echar = newEntities[key];
153 entityToChar[key] = echar;
153 entityToChar[key] = echar;
154 charToEntity[echar] = key;
154 charToEntity[echar] = key;
155 charKeys.push(echar);
155 charKeys.push(echar);
156 entityKeys.push(key);
156 entityKeys.push(key);
157 }
157 }
158 charToEntityRegex = new RegExp('(' + charKeys.join('|') + ')', 'g');
158 charToEntityRegex = new RegExp('(' + charKeys.join('|') + ')', 'g');
159 entityToCharRegex = new RegExp('(' + entityKeys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
159 entityToCharRegex = new RegExp('(' + entityKeys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
160 }
160 }
161
161
162 function htmlEncode(value){
162 function htmlEncode(value){
163 var htmlEncodeReplaceFn = function(match, capture) {
163 var htmlEncodeReplaceFn = function(match, capture) {
164 return charToEntity[capture];
164 return charToEntity[capture];
165 };
165 };
166
166
167 return (!value) ? value : String(value).replace(charToEntityRegex, htmlEncodeReplaceFn);
167 return (!value) ? value : String(value).replace(charToEntityRegex, htmlEncodeReplaceFn);
168 }
168 }
169
169
170 function htmlDecode(value) {
170 function htmlDecode(value) {
171 var htmlDecodeReplaceFn = function(match, capture) {
171 var htmlDecodeReplaceFn = function(match, capture) {
172 return (capture in entityToChar) ? entityToChar[capture] : String.fromCharCode(parseInt(capture.substr(2), 10));
172 return (capture in entityToChar) ? entityToChar[capture] : String.fromCharCode(parseInt(capture.substr(2), 10));
173 };
173 };
174
174
175 return (!value) ? value : String(value).replace(entityToCharRegex, htmlDecodeReplaceFn);
175 return (!value) ? value : String(value).replace(entityToCharRegex, htmlDecodeReplaceFn);
176 }
176 }
177
177
178 resetCharacterEntities();
178 resetCharacterEntities();
179
179
180 return {
180 return {
181 htmlEncode: htmlEncode,
181 htmlEncode: htmlEncode,
182 htmlDecode: htmlDecode
182 htmlDecode: htmlDecode
183 };
183 };
184 })();
184 })();
185
185
186 function b64DecodeUnicode(str) {
186 function b64DecodeUnicode(str) {
187 return decodeURIComponent(atob(str).split('').map(function (c) {
187 return decodeURIComponent(atob(str).split('').map(function (c) {
188 return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
188 return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
189 }).join(''));
189 }).join(''));
190 }
190 }
191
192 function utf8ToB64( str ) {
193 return window.btoa(unescape(encodeURIComponent( str )));
194 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now