##// END OF EJS Templates
comments: allow to properly initialize outdated comments that are attached...
marcink -
r2376:90cdfde6 default
parent child Browse files
Show More
@@ -1,830 +1,809 b''
1 // # Copyright (C) 2010-2017 RhodeCode GmbH
1 // # Copyright (C) 2010-2017 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 /* Comment form for main and inline comments */
45 /* Comment form for main and inline comments */
46 (function(mod) {
46 (function(mod) {
47
47
48 if (typeof exports == "object" && typeof module == "object") {
48 if (typeof exports == "object" && typeof module == "object") {
49 // CommonJS
49 // CommonJS
50 module.exports = mod();
50 module.exports = mod();
51 }
51 }
52 else {
52 else {
53 // Plain browser env
53 // Plain browser env
54 (this || window).CommentForm = mod();
54 (this || window).CommentForm = mod();
55 }
55 }
56
56
57 })(function() {
57 })(function() {
58 "use strict";
58 "use strict";
59
59
60 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId) {
60 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId) {
61 if (!(this instanceof CommentForm)) {
61 if (!(this instanceof CommentForm)) {
62 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId);
62 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId);
63 }
63 }
64
64
65 // bind the element instance to our Form
65 // bind the element instance to our Form
66 $(formElement).get(0).CommentForm = this;
66 $(formElement).get(0).CommentForm = this;
67
67
68 this.withLineNo = function(selector) {
68 this.withLineNo = function(selector) {
69 var lineNo = this.lineNo;
69 var lineNo = this.lineNo;
70 if (lineNo === undefined) {
70 if (lineNo === undefined) {
71 return selector
71 return selector
72 } else {
72 } else {
73 return selector + '_' + lineNo;
73 return selector + '_' + lineNo;
74 }
74 }
75 };
75 };
76
76
77 this.commitId = commitId;
77 this.commitId = commitId;
78 this.pullRequestId = pullRequestId;
78 this.pullRequestId = pullRequestId;
79 this.lineNo = lineNo;
79 this.lineNo = lineNo;
80 this.initAutocompleteActions = initAutocompleteActions;
80 this.initAutocompleteActions = initAutocompleteActions;
81
81
82 this.previewButton = this.withLineNo('#preview-btn');
82 this.previewButton = this.withLineNo('#preview-btn');
83 this.previewContainer = this.withLineNo('#preview-container');
83 this.previewContainer = this.withLineNo('#preview-container');
84
84
85 this.previewBoxSelector = this.withLineNo('#preview-box');
85 this.previewBoxSelector = this.withLineNo('#preview-box');
86
86
87 this.editButton = this.withLineNo('#edit-btn');
87 this.editButton = this.withLineNo('#edit-btn');
88 this.editContainer = this.withLineNo('#edit-container');
88 this.editContainer = this.withLineNo('#edit-container');
89 this.cancelButton = this.withLineNo('#cancel-btn');
89 this.cancelButton = this.withLineNo('#cancel-btn');
90 this.commentType = this.withLineNo('#comment_type');
90 this.commentType = this.withLineNo('#comment_type');
91
91
92 this.resolvesId = null;
92 this.resolvesId = null;
93 this.resolvesActionId = null;
93 this.resolvesActionId = null;
94
94
95 this.closesPr = '#close_pull_request';
95 this.closesPr = '#close_pull_request';
96
96
97 this.cmBox = this.withLineNo('#text');
97 this.cmBox = this.withLineNo('#text');
98 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
98 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
99
99
100 this.statusChange = this.withLineNo('#change_status');
100 this.statusChange = this.withLineNo('#change_status');
101
101
102 this.submitForm = formElement;
102 this.submitForm = formElement;
103 this.submitButton = $(this.submitForm).find('input[type="submit"]');
103 this.submitButton = $(this.submitForm).find('input[type="submit"]');
104 this.submitButtonText = this.submitButton.val();
104 this.submitButtonText = this.submitButton.val();
105
105
106 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
106 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
107 {'repo_name': templateContext.repo_name,
107 {'repo_name': templateContext.repo_name,
108 'commit_id': templateContext.commit_data.commit_id});
108 'commit_id': templateContext.commit_data.commit_id});
109
109
110 if (resolvesCommentId){
110 if (resolvesCommentId){
111 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
111 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
112 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
112 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
113 $(this.commentType).prop('disabled', true);
113 $(this.commentType).prop('disabled', true);
114 $(this.commentType).addClass('disabled');
114 $(this.commentType).addClass('disabled');
115
115
116 // disable select
116 // disable select
117 setTimeout(function() {
117 setTimeout(function() {
118 $(self.statusChange).select2('readonly', true);
118 $(self.statusChange).select2('readonly', true);
119 }, 10);
119 }, 10);
120
120
121 var resolvedInfo = (
121 var resolvedInfo = (
122 '<li class="resolve-action">' +
122 '<li class="resolve-action">' +
123 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
123 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
124 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
124 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
125 '</li>'
125 '</li>'
126 ).format(resolvesCommentId, _gettext('resolve comment'));
126 ).format(resolvesCommentId, _gettext('resolve comment'));
127 $(resolvedInfo).insertAfter($(this.commentType).parent());
127 $(resolvedInfo).insertAfter($(this.commentType).parent());
128 }
128 }
129
129
130 // based on commitId, or pullRequestId decide where do we submit
130 // based on commitId, or pullRequestId decide where do we submit
131 // out data
131 // out data
132 if (this.commitId){
132 if (this.commitId){
133 this.submitUrl = pyroutes.url('repo_commit_comment_create',
133 this.submitUrl = pyroutes.url('repo_commit_comment_create',
134 {'repo_name': templateContext.repo_name,
134 {'repo_name': templateContext.repo_name,
135 'commit_id': this.commitId});
135 'commit_id': this.commitId});
136 this.selfUrl = pyroutes.url('repo_commit',
136 this.selfUrl = pyroutes.url('repo_commit',
137 {'repo_name': templateContext.repo_name,
137 {'repo_name': templateContext.repo_name,
138 'commit_id': this.commitId});
138 'commit_id': this.commitId});
139
139
140 } else if (this.pullRequestId) {
140 } else if (this.pullRequestId) {
141 this.submitUrl = pyroutes.url('pullrequest_comment_create',
141 this.submitUrl = pyroutes.url('pullrequest_comment_create',
142 {'repo_name': templateContext.repo_name,
142 {'repo_name': templateContext.repo_name,
143 'pull_request_id': this.pullRequestId});
143 'pull_request_id': this.pullRequestId});
144 this.selfUrl = pyroutes.url('pullrequest_show',
144 this.selfUrl = pyroutes.url('pullrequest_show',
145 {'repo_name': templateContext.repo_name,
145 {'repo_name': templateContext.repo_name,
146 'pull_request_id': this.pullRequestId});
146 'pull_request_id': this.pullRequestId});
147
147
148 } else {
148 } else {
149 throw new Error(
149 throw new Error(
150 'CommentForm requires pullRequestId, or commitId to be specified.')
150 'CommentForm requires pullRequestId, or commitId to be specified.')
151 }
151 }
152
152
153 // FUNCTIONS and helpers
153 // FUNCTIONS and helpers
154 var self = this;
154 var self = this;
155
155
156 this.isInline = function(){
156 this.isInline = function(){
157 return this.lineNo && this.lineNo != 'general';
157 return this.lineNo && this.lineNo != 'general';
158 };
158 };
159
159
160 this.getCmInstance = function(){
160 this.getCmInstance = function(){
161 return this.cm
161 return this.cm
162 };
162 };
163
163
164 this.setPlaceholder = function(placeholder) {
164 this.setPlaceholder = function(placeholder) {
165 var cm = this.getCmInstance();
165 var cm = this.getCmInstance();
166 if (cm){
166 if (cm){
167 cm.setOption('placeholder', placeholder);
167 cm.setOption('placeholder', placeholder);
168 }
168 }
169 };
169 };
170
170
171 this.getCommentStatus = function() {
171 this.getCommentStatus = function() {
172 return $(this.submitForm).find(this.statusChange).val();
172 return $(this.submitForm).find(this.statusChange).val();
173 };
173 };
174 this.getCommentType = function() {
174 this.getCommentType = function() {
175 return $(this.submitForm).find(this.commentType).val();
175 return $(this.submitForm).find(this.commentType).val();
176 };
176 };
177
177
178 this.getResolvesId = function() {
178 this.getResolvesId = function() {
179 return $(this.submitForm).find(this.resolvesId).val() || null;
179 return $(this.submitForm).find(this.resolvesId).val() || null;
180 };
180 };
181
181
182 this.getClosePr = function() {
182 this.getClosePr = function() {
183 return $(this.submitForm).find(this.closesPr).val() || null;
183 return $(this.submitForm).find(this.closesPr).val() || null;
184 };
184 };
185
185
186 this.markCommentResolved = function(resolvedCommentId){
186 this.markCommentResolved = function(resolvedCommentId){
187 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
187 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
188 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
188 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
189 };
189 };
190
190
191 this.isAllowedToSubmit = function() {
191 this.isAllowedToSubmit = function() {
192 return !$(this.submitButton).prop('disabled');
192 return !$(this.submitButton).prop('disabled');
193 };
193 };
194
194
195 this.initStatusChangeSelector = function(){
195 this.initStatusChangeSelector = function(){
196 var formatChangeStatus = function(state, escapeMarkup) {
196 var formatChangeStatus = function(state, escapeMarkup) {
197 var originalOption = state.element;
197 var originalOption = state.element;
198 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
198 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
199 '<span>' + escapeMarkup(state.text) + '</span>';
199 '<span>' + escapeMarkup(state.text) + '</span>';
200 };
200 };
201 var formatResult = function(result, container, query, escapeMarkup) {
201 var formatResult = function(result, container, query, escapeMarkup) {
202 return formatChangeStatus(result, escapeMarkup);
202 return formatChangeStatus(result, escapeMarkup);
203 };
203 };
204
204
205 var formatSelection = function(data, container, escapeMarkup) {
205 var formatSelection = function(data, container, escapeMarkup) {
206 return formatChangeStatus(data, escapeMarkup);
206 return formatChangeStatus(data, escapeMarkup);
207 };
207 };
208
208
209 $(this.submitForm).find(this.statusChange).select2({
209 $(this.submitForm).find(this.statusChange).select2({
210 placeholder: _gettext('Status Review'),
210 placeholder: _gettext('Status Review'),
211 formatResult: formatResult,
211 formatResult: formatResult,
212 formatSelection: formatSelection,
212 formatSelection: formatSelection,
213 containerCssClass: "drop-menu status_box_menu",
213 containerCssClass: "drop-menu status_box_menu",
214 dropdownCssClass: "drop-menu-dropdown",
214 dropdownCssClass: "drop-menu-dropdown",
215 dropdownAutoWidth: true,
215 dropdownAutoWidth: true,
216 minimumResultsForSearch: -1
216 minimumResultsForSearch: -1
217 });
217 });
218 $(this.submitForm).find(this.statusChange).on('change', function() {
218 $(this.submitForm).find(this.statusChange).on('change', function() {
219 var status = self.getCommentStatus();
219 var status = self.getCommentStatus();
220
220
221 if (status && !self.isInline()) {
221 if (status && !self.isInline()) {
222 $(self.submitButton).prop('disabled', false);
222 $(self.submitButton).prop('disabled', false);
223 }
223 }
224
224
225 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
225 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
226 self.setPlaceholder(placeholderText)
226 self.setPlaceholder(placeholderText)
227 })
227 })
228 };
228 };
229
229
230 // reset the comment form into it's original state
230 // reset the comment form into it's original state
231 this.resetCommentFormState = function(content) {
231 this.resetCommentFormState = function(content) {
232 content = content || '';
232 content = content || '';
233
233
234 $(this.editContainer).show();
234 $(this.editContainer).show();
235 $(this.editButton).parent().addClass('active');
235 $(this.editButton).parent().addClass('active');
236
236
237 $(this.previewContainer).hide();
237 $(this.previewContainer).hide();
238 $(this.previewButton).parent().removeClass('active');
238 $(this.previewButton).parent().removeClass('active');
239
239
240 this.setActionButtonsDisabled(true);
240 this.setActionButtonsDisabled(true);
241 self.cm.setValue(content);
241 self.cm.setValue(content);
242 self.cm.setOption("readOnly", false);
242 self.cm.setOption("readOnly", false);
243
243
244 if (this.resolvesId) {
244 if (this.resolvesId) {
245 // destroy the resolve action
245 // destroy the resolve action
246 $(this.resolvesId).parent().remove();
246 $(this.resolvesId).parent().remove();
247 }
247 }
248 // reset closingPR flag
248 // reset closingPR flag
249 $('.close-pr-input').remove();
249 $('.close-pr-input').remove();
250
250
251 $(this.statusChange).select2('readonly', false);
251 $(this.statusChange).select2('readonly', false);
252 };
252 };
253
253
254 this.globalSubmitSuccessCallback = function(){
254 this.globalSubmitSuccessCallback = function(){
255 // default behaviour is to call GLOBAL hook, if it's registered.
255 // default behaviour is to call GLOBAL hook, if it's registered.
256 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
256 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
257 commentFormGlobalSubmitSuccessCallback()
257 commentFormGlobalSubmitSuccessCallback()
258 }
258 }
259 };
259 };
260
260
261 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
261 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
262 failHandler = failHandler || function() {};
262 failHandler = failHandler || function() {};
263 var postData = toQueryString(postData);
263 var postData = toQueryString(postData);
264 var request = $.ajax({
264 var request = $.ajax({
265 url: url,
265 url: url,
266 type: 'POST',
266 type: 'POST',
267 data: postData,
267 data: postData,
268 headers: {'X-PARTIAL-XHR': true}
268 headers: {'X-PARTIAL-XHR': true}
269 })
269 })
270 .done(function(data) {
270 .done(function(data) {
271 successHandler(data);
271 successHandler(data);
272 })
272 })
273 .fail(function(data, textStatus, errorThrown){
273 .fail(function(data, textStatus, errorThrown){
274 alert(
274 alert(
275 "Error while submitting comment.\n" +
275 "Error while submitting comment.\n" +
276 "Error code {0} ({1}).".format(data.status, data.statusText));
276 "Error code {0} ({1}).".format(data.status, data.statusText));
277 failHandler()
277 failHandler()
278 });
278 });
279 return request;
279 return request;
280 };
280 };
281
281
282 // overwrite a submitHandler, we need to do it for inline comments
282 // overwrite a submitHandler, we need to do it for inline comments
283 this.setHandleFormSubmit = function(callback) {
283 this.setHandleFormSubmit = function(callback) {
284 this.handleFormSubmit = callback;
284 this.handleFormSubmit = callback;
285 };
285 };
286
286
287 // overwrite a submitSuccessHandler
287 // overwrite a submitSuccessHandler
288 this.setGlobalSubmitSuccessCallback = function(callback) {
288 this.setGlobalSubmitSuccessCallback = function(callback) {
289 this.globalSubmitSuccessCallback = callback;
289 this.globalSubmitSuccessCallback = callback;
290 };
290 };
291
291
292 // default handler for for submit for main comments
292 // default handler for for submit for main comments
293 this.handleFormSubmit = function() {
293 this.handleFormSubmit = function() {
294 var text = self.cm.getValue();
294 var text = self.cm.getValue();
295 var status = self.getCommentStatus();
295 var status = self.getCommentStatus();
296 var commentType = self.getCommentType();
296 var commentType = self.getCommentType();
297 var resolvesCommentId = self.getResolvesId();
297 var resolvesCommentId = self.getResolvesId();
298 var closePullRequest = self.getClosePr();
298 var closePullRequest = self.getClosePr();
299
299
300 if (text === "" && !status) {
300 if (text === "" && !status) {
301 return;
301 return;
302 }
302 }
303
303
304 var excludeCancelBtn = false;
304 var excludeCancelBtn = false;
305 var submitEvent = true;
305 var submitEvent = true;
306 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
306 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
307 self.cm.setOption("readOnly", true);
307 self.cm.setOption("readOnly", true);
308
308
309 var postData = {
309 var postData = {
310 'text': text,
310 'text': text,
311 'changeset_status': status,
311 'changeset_status': status,
312 'comment_type': commentType,
312 'comment_type': commentType,
313 'csrf_token': CSRF_TOKEN
313 'csrf_token': CSRF_TOKEN
314 };
314 };
315
315
316 if (resolvesCommentId) {
316 if (resolvesCommentId) {
317 postData['resolves_comment_id'] = resolvesCommentId;
317 postData['resolves_comment_id'] = resolvesCommentId;
318 }
318 }
319
319
320 if (closePullRequest) {
320 if (closePullRequest) {
321 postData['close_pull_request'] = true;
321 postData['close_pull_request'] = true;
322 }
322 }
323
323
324 var submitSuccessCallback = function(o) {
324 var submitSuccessCallback = function(o) {
325 // reload page if we change status for single commit.
325 // reload page if we change status for single commit.
326 if (status && self.commitId) {
326 if (status && self.commitId) {
327 location.reload(true);
327 location.reload(true);
328 } else {
328 } else {
329 $('#injected_page_comments').append(o.rendered_text);
329 $('#injected_page_comments').append(o.rendered_text);
330 self.resetCommentFormState();
330 self.resetCommentFormState();
331 timeagoActivate();
331 timeagoActivate();
332
332
333 // mark visually which comment was resolved
333 // mark visually which comment was resolved
334 if (resolvesCommentId) {
334 if (resolvesCommentId) {
335 self.markCommentResolved(resolvesCommentId);
335 self.markCommentResolved(resolvesCommentId);
336 }
336 }
337 }
337 }
338
338
339 // run global callback on submit
339 // run global callback on submit
340 self.globalSubmitSuccessCallback();
340 self.globalSubmitSuccessCallback();
341
341
342 };
342 };
343 var submitFailCallback = function(){
343 var submitFailCallback = function(){
344 self.resetCommentFormState(text);
344 self.resetCommentFormState(text);
345 };
345 };
346 self.submitAjaxPOST(
346 self.submitAjaxPOST(
347 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
347 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
348 };
348 };
349
349
350 this.previewSuccessCallback = function(o) {
350 this.previewSuccessCallback = function(o) {
351 $(self.previewBoxSelector).html(o);
351 $(self.previewBoxSelector).html(o);
352 $(self.previewBoxSelector).removeClass('unloaded');
352 $(self.previewBoxSelector).removeClass('unloaded');
353
353
354 // swap buttons, making preview active
354 // swap buttons, making preview active
355 $(self.previewButton).parent().addClass('active');
355 $(self.previewButton).parent().addClass('active');
356 $(self.editButton).parent().removeClass('active');
356 $(self.editButton).parent().removeClass('active');
357
357
358 // unlock buttons
358 // unlock buttons
359 self.setActionButtonsDisabled(false);
359 self.setActionButtonsDisabled(false);
360 };
360 };
361
361
362 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
362 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
363 excludeCancelBtn = excludeCancelBtn || false;
363 excludeCancelBtn = excludeCancelBtn || false;
364 submitEvent = submitEvent || false;
364 submitEvent = submitEvent || false;
365
365
366 $(this.editButton).prop('disabled', state);
366 $(this.editButton).prop('disabled', state);
367 $(this.previewButton).prop('disabled', state);
367 $(this.previewButton).prop('disabled', state);
368
368
369 if (!excludeCancelBtn) {
369 if (!excludeCancelBtn) {
370 $(this.cancelButton).prop('disabled', state);
370 $(this.cancelButton).prop('disabled', state);
371 }
371 }
372
372
373 var submitState = state;
373 var submitState = state;
374 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
374 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
375 // if the value of commit review status is set, we allow
375 // if the value of commit review status is set, we allow
376 // submit button, but only on Main form, isInline means inline
376 // submit button, but only on Main form, isInline means inline
377 submitState = false
377 submitState = false
378 }
378 }
379
379
380 $(this.submitButton).prop('disabled', submitState);
380 $(this.submitButton).prop('disabled', submitState);
381 if (submitEvent) {
381 if (submitEvent) {
382 $(this.submitButton).val(_gettext('Submitting...'));
382 $(this.submitButton).val(_gettext('Submitting...'));
383 } else {
383 } else {
384 $(this.submitButton).val(this.submitButtonText);
384 $(this.submitButton).val(this.submitButtonText);
385 }
385 }
386
386
387 };
387 };
388
388
389 // lock preview/edit/submit buttons on load, but exclude cancel button
389 // lock preview/edit/submit buttons on load, but exclude cancel button
390 var excludeCancelBtn = true;
390 var excludeCancelBtn = true;
391 this.setActionButtonsDisabled(true, excludeCancelBtn);
391 this.setActionButtonsDisabled(true, excludeCancelBtn);
392
392
393 // anonymous users don't have access to initialized CM instance
393 // anonymous users don't have access to initialized CM instance
394 if (this.cm !== undefined){
394 if (this.cm !== undefined){
395 this.cm.on('change', function(cMirror) {
395 this.cm.on('change', function(cMirror) {
396 if (cMirror.getValue() === "") {
396 if (cMirror.getValue() === "") {
397 self.setActionButtonsDisabled(true, excludeCancelBtn)
397 self.setActionButtonsDisabled(true, excludeCancelBtn)
398 } else {
398 } else {
399 self.setActionButtonsDisabled(false, excludeCancelBtn)
399 self.setActionButtonsDisabled(false, excludeCancelBtn)
400 }
400 }
401 });
401 });
402 }
402 }
403
403
404 $(this.editButton).on('click', function(e) {
404 $(this.editButton).on('click', function(e) {
405 e.preventDefault();
405 e.preventDefault();
406
406
407 $(self.previewButton).parent().removeClass('active');
407 $(self.previewButton).parent().removeClass('active');
408 $(self.previewContainer).hide();
408 $(self.previewContainer).hide();
409
409
410 $(self.editButton).parent().addClass('active');
410 $(self.editButton).parent().addClass('active');
411 $(self.editContainer).show();
411 $(self.editContainer).show();
412
412
413 });
413 });
414
414
415 $(this.previewButton).on('click', function(e) {
415 $(this.previewButton).on('click', function(e) {
416 e.preventDefault();
416 e.preventDefault();
417 var text = self.cm.getValue();
417 var text = self.cm.getValue();
418
418
419 if (text === "") {
419 if (text === "") {
420 return;
420 return;
421 }
421 }
422
422
423 var postData = {
423 var postData = {
424 'text': text,
424 'text': text,
425 'renderer': templateContext.visual.default_renderer,
425 'renderer': templateContext.visual.default_renderer,
426 'csrf_token': CSRF_TOKEN
426 'csrf_token': CSRF_TOKEN
427 };
427 };
428
428
429 // lock ALL buttons on preview
429 // lock ALL buttons on preview
430 self.setActionButtonsDisabled(true);
430 self.setActionButtonsDisabled(true);
431
431
432 $(self.previewBoxSelector).addClass('unloaded');
432 $(self.previewBoxSelector).addClass('unloaded');
433 $(self.previewBoxSelector).html(_gettext('Loading ...'));
433 $(self.previewBoxSelector).html(_gettext('Loading ...'));
434
434
435 $(self.editContainer).hide();
435 $(self.editContainer).hide();
436 $(self.previewContainer).show();
436 $(self.previewContainer).show();
437
437
438 // by default we reset state of comment preserving the text
438 // by default we reset state of comment preserving the text
439 var previewFailCallback = function(){
439 var previewFailCallback = function(){
440 self.resetCommentFormState(text)
440 self.resetCommentFormState(text)
441 };
441 };
442 self.submitAjaxPOST(
442 self.submitAjaxPOST(
443 self.previewUrl, postData, self.previewSuccessCallback,
443 self.previewUrl, postData, self.previewSuccessCallback,
444 previewFailCallback);
444 previewFailCallback);
445
445
446 $(self.previewButton).parent().addClass('active');
446 $(self.previewButton).parent().addClass('active');
447 $(self.editButton).parent().removeClass('active');
447 $(self.editButton).parent().removeClass('active');
448 });
448 });
449
449
450 $(this.submitForm).submit(function(e) {
450 $(this.submitForm).submit(function(e) {
451 e.preventDefault();
451 e.preventDefault();
452 var allowedToSubmit = self.isAllowedToSubmit();
452 var allowedToSubmit = self.isAllowedToSubmit();
453 if (!allowedToSubmit){
453 if (!allowedToSubmit){
454 return false;
454 return false;
455 }
455 }
456 self.handleFormSubmit();
456 self.handleFormSubmit();
457 });
457 });
458
458
459 }
459 }
460
460
461 return CommentForm;
461 return CommentForm;
462 });
462 });
463
463
464 /* comments controller */
464 /* comments controller */
465 var CommentsController = function() {
465 var CommentsController = function() {
466 var mainComment = '#text';
466 var mainComment = '#text';
467 var self = this;
467 var self = this;
468
468
469 this.cancelComment = function(node) {
469 this.cancelComment = function(node) {
470 var $node = $(node);
470 var $node = $(node);
471 var $td = $node.closest('td');
471 var $td = $node.closest('td');
472 $node.closest('.comment-inline-form').remove();
472 $node.closest('.comment-inline-form').remove();
473 return false;
473 return false;
474 };
474 };
475
475
476 this.getLineNumber = function(node) {
476 this.getLineNumber = function(node) {
477 var $node = $(node);
477 var $node = $(node);
478 return $node.closest('td').attr('data-line-number');
478 var lineNo = $node.closest('td').attr('data-line-number');
479 if (lineNo === undefined && $node.data('commentInline')){
480 lineNo = $node.data('commentLineNo')
481 }
482
483 return lineNo
479 };
484 };
480
485
481 this.scrollToComment = function(node, offset, outdated) {
486 this.scrollToComment = function(node, offset, outdated) {
482 if (offset === undefined) {
487 if (offset === undefined) {
483 offset = 0;
488 offset = 0;
484 }
489 }
485 var outdated = outdated || false;
490 var outdated = outdated || false;
486 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
491 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
487
492
488 if (!node) {
493 if (!node) {
489 node = $('.comment-selected');
494 node = $('.comment-selected');
490 if (!node.length) {
495 if (!node.length) {
491 node = $('comment-current')
496 node = $('comment-current')
492 }
497 }
493 }
498 }
494 $wrapper = $(node).closest('div.comment');
499 $wrapper = $(node).closest('div.comment');
495 $comment = $(node).closest(klass);
500 $comment = $(node).closest(klass);
496 $comments = $(klass);
501 $comments = $(klass);
497
502
498 // show hidden comment when referenced.
503 // show hidden comment when referenced.
499 if (!$wrapper.is(':visible')){
504 if (!$wrapper.is(':visible')){
500 $wrapper.show();
505 $wrapper.show();
501 }
506 }
502
507
503 $('.comment-selected').removeClass('comment-selected');
508 $('.comment-selected').removeClass('comment-selected');
504
509
505 var nextIdx = $(klass).index($comment) + offset;
510 var nextIdx = $(klass).index($comment) + offset;
506 if (nextIdx >= $comments.length) {
511 if (nextIdx >= $comments.length) {
507 nextIdx = 0;
512 nextIdx = 0;
508 }
513 }
509 var $next = $(klass).eq(nextIdx);
514 var $next = $(klass).eq(nextIdx);
510
515
511 var $cb = $next.closest('.cb');
516 var $cb = $next.closest('.cb');
512 $cb.removeClass('cb-collapsed');
517 $cb.removeClass('cb-collapsed');
513
518
514 var $filediffCollapseState = $cb.closest('.filediff').prev();
519 var $filediffCollapseState = $cb.closest('.filediff').prev();
515 $filediffCollapseState.prop('checked', false);
520 $filediffCollapseState.prop('checked', false);
516 $next.addClass('comment-selected');
521 $next.addClass('comment-selected');
517 scrollToElement($next);
522 scrollToElement($next);
518 return false;
523 return false;
519 };
524 };
520
525
521 this.nextComment = function(node) {
526 this.nextComment = function(node) {
522 return self.scrollToComment(node, 1);
527 return self.scrollToComment(node, 1);
523 };
528 };
524
529
525 this.prevComment = function(node) {
530 this.prevComment = function(node) {
526 return self.scrollToComment(node, -1);
531 return self.scrollToComment(node, -1);
527 };
532 };
528
533
529 this.nextOutdatedComment = function(node) {
534 this.nextOutdatedComment = function(node) {
530 return self.scrollToComment(node, 1, true);
535 return self.scrollToComment(node, 1, true);
531 };
536 };
532
537
533 this.prevOutdatedComment = function(node) {
538 this.prevOutdatedComment = function(node) {
534 return self.scrollToComment(node, -1, true);
539 return self.scrollToComment(node, -1, true);
535 };
540 };
536
541
537 this.deleteComment = function(node) {
542 this.deleteComment = function(node) {
538 if (!confirm(_gettext('Delete this comment?'))) {
543 if (!confirm(_gettext('Delete this comment?'))) {
539 return false;
544 return false;
540 }
545 }
541 var $node = $(node);
546 var $node = $(node);
542 var $td = $node.closest('td');
547 var $td = $node.closest('td');
543 var $comment = $node.closest('.comment');
548 var $comment = $node.closest('.comment');
544 var comment_id = $comment.attr('data-comment-id');
549 var comment_id = $comment.attr('data-comment-id');
545 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
550 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
546 var postData = {
551 var postData = {
547 'csrf_token': CSRF_TOKEN
552 'csrf_token': CSRF_TOKEN
548 };
553 };
549
554
550 $comment.addClass('comment-deleting');
555 $comment.addClass('comment-deleting');
551 $comment.hide('fast');
556 $comment.hide('fast');
552
557
553 var success = function(response) {
558 var success = function(response) {
554 $comment.remove();
559 $comment.remove();
555 return false;
560 return false;
556 };
561 };
557 var failure = function(data, textStatus, xhr) {
562 var failure = function(data, textStatus, xhr) {
558 alert("error processing request: " + textStatus);
563 alert("error processing request: " + textStatus);
559 $comment.show('fast');
564 $comment.show('fast');
560 $comment.removeClass('comment-deleting');
565 $comment.removeClass('comment-deleting');
561 return false;
566 return false;
562 };
567 };
563 ajaxPOST(url, postData, success, failure);
568 ajaxPOST(url, postData, success, failure);
564 };
569 };
565
570
566 this.toggleWideMode = function (node) {
571 this.toggleWideMode = function (node) {
567 if ($('#content').hasClass('wrapper')) {
572 if ($('#content').hasClass('wrapper')) {
568 $('#content').removeClass("wrapper");
573 $('#content').removeClass("wrapper");
569 $('#content').addClass("wide-mode-wrapper");
574 $('#content').addClass("wide-mode-wrapper");
570 $(node).addClass('btn-success');
575 $(node).addClass('btn-success');
571 } else {
576 } else {
572 $('#content').removeClass("wide-mode-wrapper");
577 $('#content').removeClass("wide-mode-wrapper");
573 $('#content').addClass("wrapper");
578 $('#content').addClass("wrapper");
574 $(node).removeClass('btn-success');
579 $(node).removeClass('btn-success');
575 }
580 }
576 return false;
581 return false;
577 };
582 };
578
583
579 this.toggleComments = function(node, show) {
584 this.toggleComments = function(node, show) {
580 var $filediff = $(node).closest('.filediff');
585 var $filediff = $(node).closest('.filediff');
581 if (show === true) {
586 if (show === true) {
582 $filediff.removeClass('hide-comments');
587 $filediff.removeClass('hide-comments');
583 } else if (show === false) {
588 } else if (show === false) {
584 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
589 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
585 $filediff.addClass('hide-comments');
590 $filediff.addClass('hide-comments');
586 } else {
591 } else {
587 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
592 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
588 $filediff.toggleClass('hide-comments');
593 $filediff.toggleClass('hide-comments');
589 }
594 }
590 return false;
595 return false;
591 };
596 };
592
597
593 this.toggleLineComments = function(node) {
598 this.toggleLineComments = function(node) {
594 self.toggleComments(node, true);
599 self.toggleComments(node, true);
595 var $node = $(node);
600 var $node = $(node);
596 $node.closest('tr').toggleClass('hide-line-comments');
601 $node.closest('tr').toggleClass('hide-line-comments');
597 };
602 };
598
603
599 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId){
604 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId){
600 var pullRequestId = templateContext.pull_request_data.pull_request_id;
605 var pullRequestId = templateContext.pull_request_data.pull_request_id;
601 var commitId = templateContext.commit_data.commit_id;
606 var commitId = templateContext.commit_data.commit_id;
602
607
603 var commentForm = new CommentForm(
608 var commentForm = new CommentForm(
604 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId);
609 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId);
605 var cm = commentForm.getCmInstance();
610 var cm = commentForm.getCmInstance();
606
611
607 if (resolvesCommentId){
612 if (resolvesCommentId){
608 var placeholderText = _gettext('Leave a comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
613 var placeholderText = _gettext('Leave a comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
609 }
614 }
610
615
611 setTimeout(function() {
616 setTimeout(function() {
612 // callbacks
617 // callbacks
613 if (cm !== undefined) {
618 if (cm !== undefined) {
614 commentForm.setPlaceholder(placeholderText);
619 commentForm.setPlaceholder(placeholderText);
615 if (commentForm.isInline()) {
620 if (commentForm.isInline()) {
616 cm.focus();
621 cm.focus();
617 cm.refresh();
622 cm.refresh();
618 }
623 }
619 }
624 }
620 }, 10);
625 }, 10);
621
626
622 // trigger scrolldown to the resolve comment, since it might be away
627 // trigger scrolldown to the resolve comment, since it might be away
623 // from the clicked
628 // from the clicked
624 if (resolvesCommentId){
629 if (resolvesCommentId){
625 var actionNode = $(commentForm.resolvesActionId).offset();
630 var actionNode = $(commentForm.resolvesActionId).offset();
626
631
627 setTimeout(function() {
632 setTimeout(function() {
628 if (actionNode) {
633 if (actionNode) {
629 $('body, html').animate({scrollTop: actionNode.top}, 10);
634 $('body, html').animate({scrollTop: actionNode.top}, 10);
630 }
635 }
631 }, 100);
636 }, 100);
632 }
637 }
633
638
634 return commentForm;
639 return commentForm;
635 };
640 };
636
641
637 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
642 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
638
643
639 var tmpl = $('#cb-comment-general-form-template').html();
644 var tmpl = $('#cb-comment-general-form-template').html();
640 tmpl = tmpl.format(null, 'general');
645 tmpl = tmpl.format(null, 'general');
641 var $form = $(tmpl);
646 var $form = $(tmpl);
642
647
643 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
648 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
644 var curForm = $formPlaceholder.find('form');
649 var curForm = $formPlaceholder.find('form');
645 if (curForm){
650 if (curForm){
646 curForm.remove();
651 curForm.remove();
647 }
652 }
648 $formPlaceholder.append($form);
653 $formPlaceholder.append($form);
649
654
650 var _form = $($form[0]);
655 var _form = $($form[0]);
651 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
656 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
652 var commentForm = this.createCommentForm(
657 var commentForm = this.createCommentForm(
653 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId);
658 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId);
654 commentForm.initStatusChangeSelector();
659 commentForm.initStatusChangeSelector();
655
660
656 return commentForm;
661 return commentForm;
657 };
662 };
658
663
659 this.createComment = function(node, resolutionComment) {
664 this.createComment = function(node, resolutionComment) {
660 var resolvesCommentId = resolutionComment || null;
665 var resolvesCommentId = resolutionComment || null;
661 var $node = $(node);
666 var $node = $(node);
662 var $td = $node.closest('td');
667 var $td = $node.closest('td');
663 var $form = $td.find('.comment-inline-form');
668 var $form = $td.find('.comment-inline-form');
664
669
665 if (!$form.length) {
670 if (!$form.length) {
666
671
667 var $filediff = $node.closest('.filediff');
672 var $filediff = $node.closest('.filediff');
668 $filediff.removeClass('hide-comments');
673 $filediff.removeClass('hide-comments');
669 var f_path = $filediff.attr('data-f-path');
674 var f_path = $filediff.attr('data-f-path');
670 var lineno = self.getLineNumber(node);
675 var lineno = self.getLineNumber(node);
671 // create a new HTML from template
676 // create a new HTML from template
672 var tmpl = $('#cb-comment-inline-form-template').html();
677 var tmpl = $('#cb-comment-inline-form-template').html();
673 tmpl = tmpl.format(escapeHtml(f_path), lineno);
678 tmpl = tmpl.format(escapeHtml(f_path), lineno);
674 $form = $(tmpl);
679 $form = $(tmpl);
675
680
676 var $comments = $td.find('.inline-comments');
681 var $comments = $td.find('.inline-comments');
677 if (!$comments.length) {
682 if (!$comments.length) {
678 $comments = $(
683 $comments = $(
679 $('#cb-comments-inline-container-template').html());
684 $('#cb-comments-inline-container-template').html());
680 $td.append($comments);
685 $td.append($comments);
681 }
686 }
682
687
683 $td.find('.cb-comment-add-button').before($form);
688 $td.find('.cb-comment-add-button').before($form);
684
689
685 var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
690 var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
686 var _form = $($form[0]).find('form');
691 var _form = $($form[0]).find('form');
687 var autocompleteActions = ['as_note', 'as_todo'];
692 var autocompleteActions = ['as_note', 'as_todo'];
688 var commentForm = this.createCommentForm(
693 var commentForm = this.createCommentForm(
689 _form, lineno, placeholderText, autocompleteActions, resolvesCommentId);
694 _form, lineno, placeholderText, autocompleteActions, resolvesCommentId);
690
695
691 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
696 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
692 form: _form,
697 form: _form,
693 parent: $td[0],
698 parent: $td[0],
694 lineno: lineno,
699 lineno: lineno,
695 f_path: f_path}
700 f_path: f_path}
696 );
701 );
697
702
698 // set a CUSTOM submit handler for inline comments.
703 // set a CUSTOM submit handler for inline comments.
699 commentForm.setHandleFormSubmit(function(o) {
704 commentForm.setHandleFormSubmit(function(o) {
700 var text = commentForm.cm.getValue();
705 var text = commentForm.cm.getValue();
701 var commentType = commentForm.getCommentType();
706 var commentType = commentForm.getCommentType();
702 var resolvesCommentId = commentForm.getResolvesId();
707 var resolvesCommentId = commentForm.getResolvesId();
703
708
704 if (text === "") {
709 if (text === "") {
705 return;
710 return;
706 }
711 }
707
712
708 if (lineno === undefined) {
713 if (lineno === undefined) {
709 alert('missing line !');
714 alert('missing line !');
710 return;
715 return;
711 }
716 }
712 if (f_path === undefined) {
717 if (f_path === undefined) {
713 alert('missing file path !');
718 alert('missing file path !');
714 return;
719 return;
715 }
720 }
716
721
717 var excludeCancelBtn = false;
722 var excludeCancelBtn = false;
718 var submitEvent = true;
723 var submitEvent = true;
719 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
724 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
720 commentForm.cm.setOption("readOnly", true);
725 commentForm.cm.setOption("readOnly", true);
721 var postData = {
726 var postData = {
722 'text': text,
727 'text': text,
723 'f_path': f_path,
728 'f_path': f_path,
724 'line': lineno,
729 'line': lineno,
725 'comment_type': commentType,
730 'comment_type': commentType,
726 'csrf_token': CSRF_TOKEN
731 'csrf_token': CSRF_TOKEN
727 };
732 };
728 if (resolvesCommentId){
733 if (resolvesCommentId){
729 postData['resolves_comment_id'] = resolvesCommentId;
734 postData['resolves_comment_id'] = resolvesCommentId;
730 }
735 }
731
736
732 var submitSuccessCallback = function(json_data) {
737 var submitSuccessCallback = function(json_data) {
733 $form.remove();
738 $form.remove();
734 try {
739 try {
735 var html = json_data.rendered_text;
740 var html = json_data.rendered_text;
736 var lineno = json_data.line_no;
741 var lineno = json_data.line_no;
737 var target_id = json_data.target_id;
742 var target_id = json_data.target_id;
738
743
739 $comments.find('.cb-comment-add-button').before(html);
744 $comments.find('.cb-comment-add-button').before(html);
740
745
741 //mark visually which comment was resolved
746 //mark visually which comment was resolved
742 if (resolvesCommentId) {
747 if (resolvesCommentId) {
743 commentForm.markCommentResolved(resolvesCommentId);
748 commentForm.markCommentResolved(resolvesCommentId);
744 }
749 }
745
750
746 // run global callback on submit
751 // run global callback on submit
747 commentForm.globalSubmitSuccessCallback();
752 commentForm.globalSubmitSuccessCallback();
748
753
749 } catch (e) {
754 } catch (e) {
750 console.error(e);
755 console.error(e);
751 }
756 }
752
757
753 // re trigger the linkification of next/prev navigation
758 // re trigger the linkification of next/prev navigation
754 linkifyComments($('.inline-comment-injected'));
759 linkifyComments($('.inline-comment-injected'));
755 timeagoActivate();
760 timeagoActivate();
756 commentForm.setActionButtonsDisabled(false);
761 commentForm.setActionButtonsDisabled(false);
757
762
758 };
763 };
759 var submitFailCallback = function(){
764 var submitFailCallback = function(){
760 commentForm.resetCommentFormState(text)
765 commentForm.resetCommentFormState(text)
761 };
766 };
762 commentForm.submitAjaxPOST(
767 commentForm.submitAjaxPOST(
763 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
768 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
764 });
769 });
765 }
770 }
766
771
767 $form.addClass('comment-inline-form-open');
772 $form.addClass('comment-inline-form-open');
768 };
773 };
769
774
770 this.createResolutionComment = function(commentId){
775 this.createResolutionComment = function(commentId){
771 // hide the trigger text
776 // hide the trigger text
772 $('#resolve-comment-{0}'.format(commentId)).hide();
777 $('#resolve-comment-{0}'.format(commentId)).hide();
773
778
774 var comment = $('#comment-'+commentId);
779 var comment = $('#comment-'+commentId);
775 var commentData = comment.data();
780 var commentData = comment.data();
776 if (commentData.commentInline) {
781 if (commentData.commentInline) {
777 this.createComment(comment, commentId)
782 this.createComment(comment, commentId)
778 } else {
783 } else {
779 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
784 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
780 }
785 }
781
786
782 return false;
787 return false;
783 };
788 };
784
789
785 this.submitResolution = function(commentId){
790 this.submitResolution = function(commentId){
786 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
791 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
787 var commentForm = form.get(0).CommentForm;
792 var commentForm = form.get(0).CommentForm;
788
793
789 var cm = commentForm.getCmInstance();
794 var cm = commentForm.getCmInstance();
790 var renderer = templateContext.visual.default_renderer;
795 var renderer = templateContext.visual.default_renderer;
791 if (renderer == 'rst'){
796 if (renderer == 'rst'){
792 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
797 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
793 } else if (renderer == 'markdown') {
798 } else if (renderer == 'markdown') {
794 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
799 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
795 } else {
800 } else {
796 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
801 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
797 }
802 }
798
803
799 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
804 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
800 form.submit();
805 form.submit();
801 return false;
806 return false;
802 };
807 };
803
808
804 this.renderInlineComments = function(file_comments) {
805 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
806
807 for (var i = 0; i < file_comments.length; i++) {
808 var box = file_comments[i];
809
810 var target_id = $(box).attr('target_id');
811
812 // actually comments with line numbers
813 var comments = box.children;
814
815 for (var j = 0; j < comments.length; j++) {
816 var data = {
817 'rendered_text': comments[j].outerHTML,
818 'line_no': $(comments[j]).attr('line'),
819 'target_id': target_id
820 };
821 }
822 }
823
824 // since order of injection is random, we're now re-iterating
825 // from correct order and filling in links
826 linkifyComments($('.inline-comment-injected'));
827 firefoxAnchorFix();
828 };
829
830 };
809 };
@@ -1,405 +1,406 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ## usage:
2 ## usage:
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 ## ${comment.comment_block(comment)}
4 ## ${comment.comment_block(comment)}
5 ##
5 ##
6 <%namespace name="base" file="/base/base.mako"/>
6 <%namespace name="base" file="/base/base.mako"/>
7
7
8 <%def name="comment_block(comment, inline=False)">
8 <%def name="comment_block(comment, inline=False)">
9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
10 % if inline:
10 % if inline:
11 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
11 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
12 % else:
12 % else:
13 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
13 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
14 % endif
14 % endif
15
15
16
16
17 <div class="comment
17 <div class="comment
18 ${'comment-inline' if inline else 'comment-general'}
18 ${'comment-inline' if inline else 'comment-general'}
19 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
19 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
20 id="comment-${comment.comment_id}"
20 id="comment-${comment.comment_id}"
21 line="${comment.line_no}"
21 line="${comment.line_no}"
22 data-comment-id="${comment.comment_id}"
22 data-comment-id="${comment.comment_id}"
23 data-comment-type="${comment.comment_type}"
23 data-comment-type="${comment.comment_type}"
24 data-comment-line-no="${comment.line_no}"
24 data-comment-inline=${h.json.dumps(inline)}
25 data-comment-inline=${h.json.dumps(inline)}
25 style="${'display: none;' if outdated_at_ver else ''}">
26 style="${'display: none;' if outdated_at_ver else ''}">
26
27
27 <div class="meta">
28 <div class="meta">
28 <div class="comment-type-label">
29 <div class="comment-type-label">
29 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
30 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
30 % if comment.comment_type == 'todo':
31 % if comment.comment_type == 'todo':
31 % if comment.resolved:
32 % if comment.resolved:
32 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
33 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
33 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
34 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
34 </div>
35 </div>
35 % else:
36 % else:
36 <div class="resolved tooltip" style="display: none">
37 <div class="resolved tooltip" style="display: none">
37 <span>${comment.comment_type}</span>
38 <span>${comment.comment_type}</span>
38 </div>
39 </div>
39 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
40 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
40 ${comment.comment_type}
41 ${comment.comment_type}
41 </div>
42 </div>
42 % endif
43 % endif
43 % else:
44 % else:
44 % if comment.resolved_comment:
45 % if comment.resolved_comment:
45 fix
46 fix
46 % else:
47 % else:
47 ${comment.comment_type or 'note'}
48 ${comment.comment_type or 'note'}
48 % endif
49 % endif
49 % endif
50 % endif
50 </div>
51 </div>
51 </div>
52 </div>
52
53
53 <div class="author ${'author-inline' if inline else 'author-general'}">
54 <div class="author ${'author-inline' if inline else 'author-general'}">
54 ${base.gravatar_with_user(comment.author.email, 16)}
55 ${base.gravatar_with_user(comment.author.email, 16)}
55 </div>
56 </div>
56 <div class="date">
57 <div class="date">
57 ${h.age_component(comment.modified_at, time_is_local=True)}
58 ${h.age_component(comment.modified_at, time_is_local=True)}
58 </div>
59 </div>
59 % if inline:
60 % if inline:
60 <span></span>
61 <span></span>
61 % else:
62 % else:
62 <div class="status-change">
63 <div class="status-change">
63 % if comment.pull_request:
64 % if comment.pull_request:
64 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
65 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
65 % if comment.status_change:
66 % if comment.status_change:
66 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
67 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
67 % else:
68 % else:
68 ${_('pull request #%s') % comment.pull_request.pull_request_id}
69 ${_('pull request #%s') % comment.pull_request.pull_request_id}
69 % endif
70 % endif
70 </a>
71 </a>
71 % else:
72 % else:
72 % if comment.status_change:
73 % if comment.status_change:
73 ${_('Status change on commit')}:
74 ${_('Status change on commit')}:
74 % endif
75 % endif
75 % endif
76 % endif
76 </div>
77 </div>
77 % endif
78 % endif
78
79
79 % if comment.status_change:
80 % if comment.status_change:
80 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
81 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
81 <div title="${_('Commit status')}" class="changeset-status-lbl">
82 <div title="${_('Commit status')}" class="changeset-status-lbl">
82 ${comment.status_change[0].status_lbl}
83 ${comment.status_change[0].status_lbl}
83 </div>
84 </div>
84 % endif
85 % endif
85
86
86 % if comment.resolved_comment:
87 % if comment.resolved_comment:
87 <a class="has-spacer-before" href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
88 <a class="has-spacer-before" href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
88 ${_('resolves comment #{}').format(comment.resolved_comment.comment_id)}
89 ${_('resolves comment #{}').format(comment.resolved_comment.comment_id)}
89 </a>
90 </a>
90 % endif
91 % endif
91
92
92 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
93 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
93
94
94 <div class="comment-links-block">
95 <div class="comment-links-block">
95 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
96 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
96 <span class="tag authortag tooltip" title="${_('Pull request author')}">
97 <span class="tag authortag tooltip" title="${_('Pull request author')}">
97 ${_('author')}
98 ${_('author')}
98 </span>
99 </span>
99 |
100 |
100 % endif
101 % endif
101 % if inline:
102 % if inline:
102 <div class="pr-version-inline">
103 <div class="pr-version-inline">
103 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
104 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
104 % if outdated_at_ver:
105 % if outdated_at_ver:
105 <code class="pr-version-num" title="${_('Outdated comment from pull request version {0}').format(pr_index_ver)}">
106 <code class="pr-version-num" title="${_('Outdated comment from pull request version {0}').format(pr_index_ver)}">
106 outdated ${'v{}'.format(pr_index_ver)} |
107 outdated ${'v{}'.format(pr_index_ver)} |
107 </code>
108 </code>
108 % elif pr_index_ver:
109 % elif pr_index_ver:
109 <code class="pr-version-num" title="${_('Comment from pull request version {0}').format(pr_index_ver)}">
110 <code class="pr-version-num" title="${_('Comment from pull request version {0}').format(pr_index_ver)}">
110 ${'v{}'.format(pr_index_ver)} |
111 ${'v{}'.format(pr_index_ver)} |
111 </code>
112 </code>
112 % endif
113 % endif
113 </a>
114 </a>
114 </div>
115 </div>
115 % else:
116 % else:
116 % if comment.pull_request_version_id and pr_index_ver:
117 % if comment.pull_request_version_id and pr_index_ver:
117 |
118 |
118 <div class="pr-version">
119 <div class="pr-version">
119 % if comment.outdated:
120 % if comment.outdated:
120 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
121 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
121 ${_('Outdated comment from pull request version {}').format(pr_index_ver)}
122 ${_('Outdated comment from pull request version {}').format(pr_index_ver)}
122 </a>
123 </a>
123 % else:
124 % else:
124 <div title="${_('Comment from pull request version {0}').format(pr_index_ver)}">
125 <div title="${_('Comment from pull request version {0}').format(pr_index_ver)}">
125 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
126 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
126 <code class="pr-version-num">
127 <code class="pr-version-num">
127 ${'v{}'.format(pr_index_ver)}
128 ${'v{}'.format(pr_index_ver)}
128 </code>
129 </code>
129 </a>
130 </a>
130 </div>
131 </div>
131 % endif
132 % endif
132 </div>
133 </div>
133 % endif
134 % endif
134 % endif
135 % endif
135
136
136 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
137 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
137 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
138 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
138 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
139 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
139 ## permissions to delete
140 ## permissions to delete
140 %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
141 %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
141 ## TODO: dan: add edit comment here
142 ## TODO: dan: add edit comment here
142 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
143 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
143 %else:
144 %else:
144 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
145 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
145 %endif
146 %endif
146 %else:
147 %else:
147 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
148 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
148 %endif
149 %endif
149
150
150 % if outdated_at_ver:
151 % if outdated_at_ver:
151 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
152 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
152 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
153 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
153 % else:
154 % else:
154 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
155 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
155 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
156 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
156 % endif
157 % endif
157
158
158 </div>
159 </div>
159 </div>
160 </div>
160 <div class="text">
161 <div class="text">
161 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
162 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
162 </div>
163 </div>
163
164
164 </div>
165 </div>
165 </%def>
166 </%def>
166
167
167 ## generate main comments
168 ## generate main comments
168 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
169 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
169 <div class="general-comments" id="comments">
170 <div class="general-comments" id="comments">
170 %for comment in comments:
171 %for comment in comments:
171 <div id="comment-tr-${comment.comment_id}">
172 <div id="comment-tr-${comment.comment_id}">
172 ## only render comments that are not from pull request, or from
173 ## only render comments that are not from pull request, or from
173 ## pull request and a status change
174 ## pull request and a status change
174 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
175 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
175 ${comment_block(comment)}
176 ${comment_block(comment)}
176 %endif
177 %endif
177 </div>
178 </div>
178 %endfor
179 %endfor
179 ## to anchor ajax comments
180 ## to anchor ajax comments
180 <div id="injected_page_comments"></div>
181 <div id="injected_page_comments"></div>
181 </div>
182 </div>
182 </%def>
183 </%def>
183
184
184
185
185 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
186 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
186
187
187 <div class="comments">
188 <div class="comments">
188 <%
189 <%
189 if is_pull_request:
190 if is_pull_request:
190 placeholder = _('Leave a comment on this Pull Request.')
191 placeholder = _('Leave a comment on this Pull Request.')
191 elif is_compare:
192 elif is_compare:
192 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
193 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
193 else:
194 else:
194 placeholder = _('Leave a comment on this Commit.')
195 placeholder = _('Leave a comment on this Commit.')
195 %>
196 %>
196
197
197 % if c.rhodecode_user.username != h.DEFAULT_USER:
198 % if c.rhodecode_user.username != h.DEFAULT_USER:
198 <div class="js-template" id="cb-comment-general-form-template">
199 <div class="js-template" id="cb-comment-general-form-template">
199 ## template generated for injection
200 ## template generated for injection
200 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
201 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
201 </div>
202 </div>
202
203
203 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
204 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
204 ## inject form here
205 ## inject form here
205 </div>
206 </div>
206 <script type="text/javascript">
207 <script type="text/javascript">
207 var lineNo = 'general';
208 var lineNo = 'general';
208 var resolvesCommentId = null;
209 var resolvesCommentId = null;
209 var generalCommentForm = Rhodecode.comments.createGeneralComment(
210 var generalCommentForm = Rhodecode.comments.createGeneralComment(
210 lineNo, "${placeholder}", resolvesCommentId);
211 lineNo, "${placeholder}", resolvesCommentId);
211
212
212 // set custom success callback on rangeCommit
213 // set custom success callback on rangeCommit
213 % if is_compare:
214 % if is_compare:
214 generalCommentForm.setHandleFormSubmit(function(o) {
215 generalCommentForm.setHandleFormSubmit(function(o) {
215 var self = generalCommentForm;
216 var self = generalCommentForm;
216
217
217 var text = self.cm.getValue();
218 var text = self.cm.getValue();
218 var status = self.getCommentStatus();
219 var status = self.getCommentStatus();
219 var commentType = self.getCommentType();
220 var commentType = self.getCommentType();
220
221
221 if (text === "" && !status) {
222 if (text === "" && !status) {
222 return;
223 return;
223 }
224 }
224
225
225 // we can pick which commits we want to make the comment by
226 // we can pick which commits we want to make the comment by
226 // selecting them via click on preview pane, this will alter the hidden inputs
227 // selecting them via click on preview pane, this will alter the hidden inputs
227 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
228 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
228
229
229 var commitIds = [];
230 var commitIds = [];
230 $('#changeset_compare_view_content .compare_select').each(function(el) {
231 $('#changeset_compare_view_content .compare_select').each(function(el) {
231 var commitId = this.id.replace('row-', '');
232 var commitId = this.id.replace('row-', '');
232 if ($(this).hasClass('hl') || !cherryPicked) {
233 if ($(this).hasClass('hl') || !cherryPicked) {
233 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
234 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
234 commitIds.push(commitId);
235 commitIds.push(commitId);
235 } else {
236 } else {
236 $("input[data-commit-id='{0}']".format(commitId)).val('')
237 $("input[data-commit-id='{0}']".format(commitId)).val('')
237 }
238 }
238 });
239 });
239
240
240 self.setActionButtonsDisabled(true);
241 self.setActionButtonsDisabled(true);
241 self.cm.setOption("readOnly", true);
242 self.cm.setOption("readOnly", true);
242 var postData = {
243 var postData = {
243 'text': text,
244 'text': text,
244 'changeset_status': status,
245 'changeset_status': status,
245 'comment_type': commentType,
246 'comment_type': commentType,
246 'commit_ids': commitIds,
247 'commit_ids': commitIds,
247 'csrf_token': CSRF_TOKEN
248 'csrf_token': CSRF_TOKEN
248 };
249 };
249
250
250 var submitSuccessCallback = function(o) {
251 var submitSuccessCallback = function(o) {
251 location.reload(true);
252 location.reload(true);
252 };
253 };
253 var submitFailCallback = function(){
254 var submitFailCallback = function(){
254 self.resetCommentFormState(text)
255 self.resetCommentFormState(text)
255 };
256 };
256 self.submitAjaxPOST(
257 self.submitAjaxPOST(
257 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
258 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
258 });
259 });
259 % endif
260 % endif
260
261
261
262
262 </script>
263 </script>
263 % else:
264 % else:
264 ## form state when not logged in
265 ## form state when not logged in
265 <div class="comment-form ac">
266 <div class="comment-form ac">
266
267
267 <div class="comment-area">
268 <div class="comment-area">
268 <div class="comment-area-header">
269 <div class="comment-area-header">
269 <ul class="nav-links clearfix">
270 <ul class="nav-links clearfix">
270 <li class="active">
271 <li class="active">
271 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
272 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
272 </li>
273 </li>
273 <li class="">
274 <li class="">
274 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
275 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
275 </li>
276 </li>
276 </ul>
277 </ul>
277 </div>
278 </div>
278
279
279 <div class="comment-area-write" style="display: block;">
280 <div class="comment-area-write" style="display: block;">
280 <div id="edit-container">
281 <div id="edit-container">
281 <div style="padding: 40px 0">
282 <div style="padding: 40px 0">
282 ${_('You need to be logged in to leave comments.')}
283 ${_('You need to be logged in to leave comments.')}
283 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
284 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
284 </div>
285 </div>
285 </div>
286 </div>
286 <div id="preview-container" class="clearfix" style="display: none;">
287 <div id="preview-container" class="clearfix" style="display: none;">
287 <div id="preview-box" class="preview-box"></div>
288 <div id="preview-box" class="preview-box"></div>
288 </div>
289 </div>
289 </div>
290 </div>
290
291
291 <div class="comment-area-footer">
292 <div class="comment-area-footer">
292 <div class="toolbar">
293 <div class="toolbar">
293 <div class="toolbar-text">
294 <div class="toolbar-text">
294 </div>
295 </div>
295 </div>
296 </div>
296 </div>
297 </div>
297 </div>
298 </div>
298
299
299 <div class="comment-footer">
300 <div class="comment-footer">
300 </div>
301 </div>
301
302
302 </div>
303 </div>
303 % endif
304 % endif
304
305
305 <script type="text/javascript">
306 <script type="text/javascript">
306 bindToggleButtons();
307 bindToggleButtons();
307 </script>
308 </script>
308 </div>
309 </div>
309 </%def>
310 </%def>
310
311
311
312
312 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
313 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
313 ## comment injected based on assumption that user is logged in
314 ## comment injected based on assumption that user is logged in
314
315
315 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
316 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
316
317
317 <div class="comment-area">
318 <div class="comment-area">
318 <div class="comment-area-header">
319 <div class="comment-area-header">
319 <ul class="nav-links clearfix">
320 <ul class="nav-links clearfix">
320 <li class="active">
321 <li class="active">
321 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
322 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
322 </li>
323 </li>
323 <li class="">
324 <li class="">
324 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
325 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
325 </li>
326 </li>
326 <li class="pull-right">
327 <li class="pull-right">
327 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
328 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
328 % for val in c.visual.comment_types:
329 % for val in c.visual.comment_types:
329 <option value="${val}">${val.upper()}</option>
330 <option value="${val}">${val.upper()}</option>
330 % endfor
331 % endfor
331 </select>
332 </select>
332 </li>
333 </li>
333 </ul>
334 </ul>
334 </div>
335 </div>
335
336
336 <div class="comment-area-write" style="display: block;">
337 <div class="comment-area-write" style="display: block;">
337 <div id="edit-container_${lineno_id}">
338 <div id="edit-container_${lineno_id}">
338 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
339 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
339 </div>
340 </div>
340 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
341 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
341 <div id="preview-box_${lineno_id}" class="preview-box"></div>
342 <div id="preview-box_${lineno_id}" class="preview-box"></div>
342 </div>
343 </div>
343 </div>
344 </div>
344
345
345 <div class="comment-area-footer">
346 <div class="comment-area-footer">
346 <div class="toolbar">
347 <div class="toolbar">
347 <div class="toolbar-text">
348 <div class="toolbar-text">
348 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
349 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
349 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
350 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
350 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
351 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
351 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
352 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
352 )
353 )
353 )|n}
354 )|n}
354 </div>
355 </div>
355 </div>
356 </div>
356 </div>
357 </div>
357 </div>
358 </div>
358
359
359 <div class="comment-footer">
360 <div class="comment-footer">
360
361
361 % if review_statuses:
362 % if review_statuses:
362 <div class="status_box">
363 <div class="status_box">
363 <select id="change_status_${lineno_id}" name="changeset_status">
364 <select id="change_status_${lineno_id}" name="changeset_status">
364 <option></option> ## Placeholder
365 <option></option> ## Placeholder
365 % for status, lbl in review_statuses:
366 % for status, lbl in review_statuses:
366 <option value="${status}" data-status="${status}">${lbl}</option>
367 <option value="${status}" data-status="${status}">${lbl}</option>
367 %if is_pull_request and change_status and status in ('approved', 'rejected'):
368 %if is_pull_request and change_status and status in ('approved', 'rejected'):
368 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
369 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
369 %endif
370 %endif
370 % endfor
371 % endfor
371 </select>
372 </select>
372 </div>
373 </div>
373 % endif
374 % endif
374
375
375 ## inject extra inputs into the form
376 ## inject extra inputs into the form
376 % if form_extras and isinstance(form_extras, (list, tuple)):
377 % if form_extras and isinstance(form_extras, (list, tuple)):
377 <div id="comment_form_extras">
378 <div id="comment_form_extras">
378 % for form_ex_el in form_extras:
379 % for form_ex_el in form_extras:
379 ${form_ex_el|n}
380 ${form_ex_el|n}
380 % endfor
381 % endfor
381 </div>
382 </div>
382 % endif
383 % endif
383
384
384 <div class="action-buttons">
385 <div class="action-buttons">
385 ## inline for has a file, and line-number together with cancel hide button.
386 ## inline for has a file, and line-number together with cancel hide button.
386 % if form_type == 'inline':
387 % if form_type == 'inline':
387 <input type="hidden" name="f_path" value="{0}">
388 <input type="hidden" name="f_path" value="{0}">
388 <input type="hidden" name="line" value="${lineno_id}">
389 <input type="hidden" name="line" value="${lineno_id}">
389 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
390 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
390 ${_('Cancel')}
391 ${_('Cancel')}
391 </button>
392 </button>
392 % endif
393 % endif
393
394
394 % if form_type != 'inline':
395 % if form_type != 'inline':
395 <div class="action-buttons-extra"></div>
396 <div class="action-buttons-extra"></div>
396 % endif
397 % endif
397
398
398 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
399 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
399
400
400 </div>
401 </div>
401 </div>
402 </div>
402
403
403 </form>
404 </form>
404
405
405 </%def> No newline at end of file
406 </%def>
General Comments 0
You need to be logged in to leave comments. Login now