##// END OF EJS Templates
topics: improve handling topics without callbacks subscribed
ergo -
r451:5a3151b8 default
parent child Browse files
Show More
@@ -1,395 +1,395 b''
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
1 // # Copyright (C) 2010-2016 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 RhodeCode JS Files
20 RhodeCode JS Files
21 **/
21 **/
22
22
23 if (typeof console == "undefined" || typeof console.log == "undefined"){
23 if (typeof console == "undefined" || typeof console.log == "undefined"){
24 console = { log: function() {} }
24 console = { log: function() {} }
25 }
25 }
26
26
27 // TODO: move the following function to submodules
27 // TODO: move the following function to submodules
28
28
29 /**
29 /**
30 * show more
30 * show more
31 */
31 */
32 var show_more_event = function(){
32 var show_more_event = function(){
33 $('table .show_more').click(function(e) {
33 $('table .show_more').click(function(e) {
34 var cid = e.target.id.substring(1);
34 var cid = e.target.id.substring(1);
35 var button = $(this);
35 var button = $(this);
36 if (button.hasClass('open')) {
36 if (button.hasClass('open')) {
37 $('#'+cid).hide();
37 $('#'+cid).hide();
38 button.removeClass('open');
38 button.removeClass('open');
39 } else {
39 } else {
40 $('#'+cid).show();
40 $('#'+cid).show();
41 button.addClass('open one');
41 button.addClass('open one');
42 }
42 }
43 });
43 });
44 };
44 };
45
45
46 var compare_radio_buttons = function(repo_name, compare_ref_type){
46 var compare_radio_buttons = function(repo_name, compare_ref_type){
47 $('#compare_action').on('click', function(e){
47 $('#compare_action').on('click', function(e){
48 e.preventDefault();
48 e.preventDefault();
49
49
50 var source = $('input[name=compare_source]:checked').val();
50 var source = $('input[name=compare_source]:checked').val();
51 var target = $('input[name=compare_target]:checked').val();
51 var target = $('input[name=compare_target]:checked').val();
52 if(source && target){
52 if(source && target){
53 var url_data = {
53 var url_data = {
54 repo_name: repo_name,
54 repo_name: repo_name,
55 source_ref: source,
55 source_ref: source,
56 source_ref_type: compare_ref_type,
56 source_ref_type: compare_ref_type,
57 target_ref: target,
57 target_ref: target,
58 target_ref_type: compare_ref_type,
58 target_ref_type: compare_ref_type,
59 merge: 1
59 merge: 1
60 };
60 };
61 window.location = pyroutes.url('compare_url', url_data);
61 window.location = pyroutes.url('compare_url', url_data);
62 }
62 }
63 });
63 });
64 $('.compare-radio-button').on('click', function(e){
64 $('.compare-radio-button').on('click', function(e){
65 var source = $('input[name=compare_source]:checked').val();
65 var source = $('input[name=compare_source]:checked').val();
66 var target = $('input[name=compare_target]:checked').val();
66 var target = $('input[name=compare_target]:checked').val();
67 if(source && target){
67 if(source && target){
68 $('#compare_action').removeAttr("disabled");
68 $('#compare_action').removeAttr("disabled");
69 $('#compare_action').removeClass("disabled");
69 $('#compare_action').removeClass("disabled");
70 }
70 }
71 })
71 })
72 };
72 };
73
73
74 var showRepoSize = function(target, repo_name, commit_id, callback) {
74 var showRepoSize = function(target, repo_name, commit_id, callback) {
75 var container = $('#' + target);
75 var container = $('#' + target);
76 var url = pyroutes.url('repo_stats',
76 var url = pyroutes.url('repo_stats',
77 {"repo_name": repo_name, "commit_id": commit_id});
77 {"repo_name": repo_name, "commit_id": commit_id});
78
78
79 if (!container.hasClass('loaded')) {
79 if (!container.hasClass('loaded')) {
80 $.ajax({url: url})
80 $.ajax({url: url})
81 .complete(function (data) {
81 .complete(function (data) {
82 var responseJSON = data.responseJSON;
82 var responseJSON = data.responseJSON;
83 container.addClass('loaded');
83 container.addClass('loaded');
84 container.html(responseJSON.size);
84 container.html(responseJSON.size);
85 callback(responseJSON.code_stats)
85 callback(responseJSON.code_stats)
86 })
86 })
87 .fail(function (data) {
87 .fail(function (data) {
88 console.log('failed to load repo stats');
88 console.log('failed to load repo stats');
89 });
89 });
90 }
90 }
91
91
92 };
92 };
93
93
94 var showRepoStats = function(target, data){
94 var showRepoStats = function(target, data){
95 var container = $('#' + target);
95 var container = $('#' + target);
96
96
97 if (container.hasClass('loaded')) {
97 if (container.hasClass('loaded')) {
98 return
98 return
99 }
99 }
100
100
101 var total = 0;
101 var total = 0;
102 var no_data = true;
102 var no_data = true;
103 var tbl = document.createElement('table');
103 var tbl = document.createElement('table');
104 tbl.setAttribute('class', 'trending_language_tbl');
104 tbl.setAttribute('class', 'trending_language_tbl');
105
105
106 $.each(data, function(key, val){
106 $.each(data, function(key, val){
107 total += val.count;
107 total += val.count;
108 });
108 });
109
109
110 var sortedStats = [];
110 var sortedStats = [];
111 for (var obj in data){
111 for (var obj in data){
112 sortedStats.push([obj, data[obj]])
112 sortedStats.push([obj, data[obj]])
113 }
113 }
114 var sortedData = sortedStats.sort(function (a, b) {
114 var sortedData = sortedStats.sort(function (a, b) {
115 return b[1].count - a[1].count
115 return b[1].count - a[1].count
116 });
116 });
117 var cnt = 0;
117 var cnt = 0;
118 $.each(sortedData, function(idx, val){
118 $.each(sortedData, function(idx, val){
119 cnt += 1;
119 cnt += 1;
120 no_data = false;
120 no_data = false;
121
121
122 var hide = cnt > 2;
122 var hide = cnt > 2;
123 var tr = document.createElement('tr');
123 var tr = document.createElement('tr');
124 if (hide) {
124 if (hide) {
125 tr.setAttribute('style', 'display:none');
125 tr.setAttribute('style', 'display:none');
126 tr.setAttribute('class', 'stats_hidden');
126 tr.setAttribute('class', 'stats_hidden');
127 }
127 }
128
128
129 var key = val[0];
129 var key = val[0];
130 var obj = {"desc": val[1].desc, "count": val[1].count};
130 var obj = {"desc": val[1].desc, "count": val[1].count};
131
131
132 var percentage = Math.round((obj.count / total * 100), 2);
132 var percentage = Math.round((obj.count / total * 100), 2);
133
133
134 var td1 = document.createElement('td');
134 var td1 = document.createElement('td');
135 td1.width = 300;
135 td1.width = 300;
136 var trending_language_label = document.createElement('div');
136 var trending_language_label = document.createElement('div');
137 trending_language_label.innerHTML = obj.desc + " (.{0})".format(key);
137 trending_language_label.innerHTML = obj.desc + " (.{0})".format(key);
138 td1.appendChild(trending_language_label);
138 td1.appendChild(trending_language_label);
139
139
140 var td2 = document.createElement('td');
140 var td2 = document.createElement('td');
141 var trending_language = document.createElement('div');
141 var trending_language = document.createElement('div');
142 var nr_files = obj.count +" "+ _ngettext('file', 'files', obj.count);
142 var nr_files = obj.count +" "+ _ngettext('file', 'files', obj.count);
143
143
144 trending_language.title = key + " " + nr_files;
144 trending_language.title = key + " " + nr_files;
145
145
146 trending_language.innerHTML = "<span>" + percentage + "% " + nr_files
146 trending_language.innerHTML = "<span>" + percentage + "% " + nr_files
147 + "</span><b>" + percentage + "% " + nr_files + "</b>";
147 + "</span><b>" + percentage + "% " + nr_files + "</b>";
148
148
149 trending_language.setAttribute("class", 'trending_language');
149 trending_language.setAttribute("class", 'trending_language');
150 $('b', trending_language)[0].style.width = percentage + "%";
150 $('b', trending_language)[0].style.width = percentage + "%";
151 td2.appendChild(trending_language);
151 td2.appendChild(trending_language);
152
152
153 tr.appendChild(td1);
153 tr.appendChild(td1);
154 tr.appendChild(td2);
154 tr.appendChild(td2);
155 tbl.appendChild(tr);
155 tbl.appendChild(tr);
156 if (cnt == 3) {
156 if (cnt == 3) {
157 var show_more = document.createElement('tr');
157 var show_more = document.createElement('tr');
158 var td = document.createElement('td');
158 var td = document.createElement('td');
159 lnk = document.createElement('a');
159 lnk = document.createElement('a');
160
160
161 lnk.href = '#';
161 lnk.href = '#';
162 lnk.innerHTML = _gettext('Show more');
162 lnk.innerHTML = _gettext('Show more');
163 lnk.id = 'code_stats_show_more';
163 lnk.id = 'code_stats_show_more';
164 td.appendChild(lnk);
164 td.appendChild(lnk);
165
165
166 show_more.appendChild(td);
166 show_more.appendChild(td);
167 show_more.appendChild(document.createElement('td'));
167 show_more.appendChild(document.createElement('td'));
168 tbl.appendChild(show_more);
168 tbl.appendChild(show_more);
169 }
169 }
170 });
170 });
171
171
172 $(container).html(tbl);
172 $(container).html(tbl);
173 $(container).addClass('loaded');
173 $(container).addClass('loaded');
174
174
175 $('#code_stats_show_more').on('click', function (e) {
175 $('#code_stats_show_more').on('click', function (e) {
176 e.preventDefault();
176 e.preventDefault();
177 $('.stats_hidden').each(function (idx) {
177 $('.stats_hidden').each(function (idx) {
178 $(this).css("display", "");
178 $(this).css("display", "");
179 });
179 });
180 $('#code_stats_show_more').hide();
180 $('#code_stats_show_more').hide();
181 });
181 });
182
182
183 };
183 };
184
184
185
185
186 // Toggle Collapsable Content
186 // Toggle Collapsable Content
187 function collapsableContent() {
187 function collapsableContent() {
188
188
189 $('.collapsable-content').not('.no-hide').hide();
189 $('.collapsable-content').not('.no-hide').hide();
190
190
191 $('.btn-collapse').unbind(); //in case we've been here before
191 $('.btn-collapse').unbind(); //in case we've been here before
192 $('.btn-collapse').click(function() {
192 $('.btn-collapse').click(function() {
193 var button = $(this);
193 var button = $(this);
194 var togglename = $(this).data("toggle");
194 var togglename = $(this).data("toggle");
195 $('.collapsable-content[data-toggle='+togglename+']').toggle();
195 $('.collapsable-content[data-toggle='+togglename+']').toggle();
196 if ($(this).html()=="Show Less")
196 if ($(this).html()=="Show Less")
197 $(this).html("Show More");
197 $(this).html("Show More");
198 else
198 else
199 $(this).html("Show Less");
199 $(this).html("Show Less");
200 });
200 });
201 };
201 };
202
202
203 var timeagoActivate = function() {
203 var timeagoActivate = function() {
204 $("time.timeago").timeago();
204 $("time.timeago").timeago();
205 };
205 };
206
206
207 // Formatting values in a Select2 dropdown of commit references
207 // Formatting values in a Select2 dropdown of commit references
208 var formatSelect2SelectionRefs = function(commit_ref){
208 var formatSelect2SelectionRefs = function(commit_ref){
209 var tmpl = '';
209 var tmpl = '';
210 if (!commit_ref.text || commit_ref.type === 'sha'){
210 if (!commit_ref.text || commit_ref.type === 'sha'){
211 return commit_ref.text;
211 return commit_ref.text;
212 }
212 }
213 if (commit_ref.type === 'branch'){
213 if (commit_ref.type === 'branch'){
214 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
214 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
215 } else if (commit_ref.type === 'tag'){
215 } else if (commit_ref.type === 'tag'){
216 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
216 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
217 } else if (commit_ref.type === 'book'){
217 } else if (commit_ref.type === 'book'){
218 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
218 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
219 }
219 }
220 return tmpl.concat(commit_ref.text);
220 return tmpl.concat(commit_ref.text);
221 };
221 };
222
222
223 // takes a given html element and scrolls it down offset pixels
223 // takes a given html element and scrolls it down offset pixels
224 function offsetScroll(element, offset){
224 function offsetScroll(element, offset){
225 setTimeout(function(){
225 setTimeout(function(){
226 console.log(element);
226 console.log(element);
227 var location = element.offset().top;
227 var location = element.offset().top;
228 // some browsers use body, some use html
228 // some browsers use body, some use html
229 $('html, body').animate({ scrollTop: (location - offset) });
229 $('html, body').animate({ scrollTop: (location - offset) });
230 }, 100);
230 }, 100);
231 }
231 }
232
232
233 /**
233 /**
234 * global hooks after DOM is loaded
234 * global hooks after DOM is loaded
235 */
235 */
236 $(document).ready(function() {
236 $(document).ready(function() {
237 firefoxAnchorFix();
237 firefoxAnchorFix();
238
238
239 $('.navigation a.menulink').on('click', function(e){
239 $('.navigation a.menulink').on('click', function(e){
240 var menuitem = $(this).parent('li');
240 var menuitem = $(this).parent('li');
241 if (menuitem.hasClass('open')) {
241 if (menuitem.hasClass('open')) {
242 menuitem.removeClass('open');
242 menuitem.removeClass('open');
243 } else {
243 } else {
244 menuitem.addClass('open');
244 menuitem.addClass('open');
245 $(document).on('click', function(event) {
245 $(document).on('click', function(event) {
246 if (!$(event.target).closest(menuitem).length) {
246 if (!$(event.target).closest(menuitem).length) {
247 menuitem.removeClass('open');
247 menuitem.removeClass('open');
248 }
248 }
249 });
249 });
250 }
250 }
251 });
251 });
252 $('.compare_view_files').on(
252 $('.compare_view_files').on(
253 'mouseenter mouseleave', 'tr.line .lineno a',function(event) {
253 'mouseenter mouseleave', 'tr.line .lineno a',function(event) {
254 if (event.type === "mouseenter") {
254 if (event.type === "mouseenter") {
255 $(this).parents('tr.line').addClass('hover');
255 $(this).parents('tr.line').addClass('hover');
256 } else {
256 } else {
257 $(this).parents('tr.line').removeClass('hover');
257 $(this).parents('tr.line').removeClass('hover');
258 }
258 }
259 });
259 });
260
260
261 $('.compare_view_files').on(
261 $('.compare_view_files').on(
262 'mouseenter mouseleave', 'tr.line .add-comment-line a',function(event){
262 'mouseenter mouseleave', 'tr.line .add-comment-line a',function(event){
263 if (event.type === "mouseenter") {
263 if (event.type === "mouseenter") {
264 $(this).parents('tr.line').addClass('commenting');
264 $(this).parents('tr.line').addClass('commenting');
265 } else {
265 } else {
266 $(this).parents('tr.line').removeClass('commenting');
266 $(this).parents('tr.line').removeClass('commenting');
267 }
267 }
268 });
268 });
269
269
270 $('.compare_view_files').on(
270 $('.compare_view_files').on(
271 'click', 'tr.line .lineno a',function(event) {
271 'click', 'tr.line .lineno a',function(event) {
272 if ($(this).text() != ""){
272 if ($(this).text() != ""){
273 $('tr.line').removeClass('selected');
273 $('tr.line').removeClass('selected');
274 $(this).parents("tr.line").addClass('selected');
274 $(this).parents("tr.line").addClass('selected');
275
275
276 // Replace URL without jumping to it if browser supports.
276 // Replace URL without jumping to it if browser supports.
277 // Default otherwise
277 // Default otherwise
278 if (history.pushState) {
278 if (history.pushState) {
279 var new_location = location.href;
279 var new_location = location.href;
280 if (location.hash){
280 if (location.hash){
281 new_location = new_location.replace(location.hash, "");
281 new_location = new_location.replace(location.hash, "");
282 }
282 }
283
283
284 // Make new anchor url
284 // Make new anchor url
285 var new_location = new_location+$(this).attr('href');
285 var new_location = new_location+$(this).attr('href');
286 history.pushState(true, document.title, new_location);
286 history.pushState(true, document.title, new_location);
287
287
288 return false;
288 return false;
289 }
289 }
290 }
290 }
291 });
291 });
292
292
293 $('.compare_view_files').on(
293 $('.compare_view_files').on(
294 'click', 'tr.line .add-comment-line a',function(event) {
294 'click', 'tr.line .add-comment-line a',function(event) {
295 var tr = $(event.currentTarget).parents('tr.line')[0];
295 var tr = $(event.currentTarget).parents('tr.line')[0];
296 injectInlineForm(tr);
296 injectInlineForm(tr);
297 return false;
297 return false;
298 });
298 });
299
299
300 $('.collapse_file').on('click', function(e) {
300 $('.collapse_file').on('click', function(e) {
301 e.stopPropagation();
301 e.stopPropagation();
302 if ($(e.target).is('a')) { return; }
302 if ($(e.target).is('a')) { return; }
303 var node = $(e.delegateTarget).first();
303 var node = $(e.delegateTarget).first();
304 var icon = $($(node.children().first()).children().first());
304 var icon = $($(node.children().first()).children().first());
305 var id = node.attr('fid');
305 var id = node.attr('fid');
306 var target = $('#'+id);
306 var target = $('#'+id);
307 var tr = $('#tr_'+id);
307 var tr = $('#tr_'+id);
308 var diff = $('#diff_'+id);
308 var diff = $('#diff_'+id);
309 if(node.hasClass('expand_file')){
309 if(node.hasClass('expand_file')){
310 node.removeClass('expand_file');
310 node.removeClass('expand_file');
311 icon.removeClass('expand_file_icon');
311 icon.removeClass('expand_file_icon');
312 node.addClass('collapse_file');
312 node.addClass('collapse_file');
313 icon.addClass('collapse_file_icon');
313 icon.addClass('collapse_file_icon');
314 diff.show();
314 diff.show();
315 tr.show();
315 tr.show();
316 target.show();
316 target.show();
317 } else {
317 } else {
318 node.removeClass('collapse_file');
318 node.removeClass('collapse_file');
319 icon.removeClass('collapse_file_icon');
319 icon.removeClass('collapse_file_icon');
320 node.addClass('expand_file');
320 node.addClass('expand_file');
321 icon.addClass('expand_file_icon');
321 icon.addClass('expand_file_icon');
322 diff.hide();
322 diff.hide();
323 tr.hide();
323 tr.hide();
324 target.hide();
324 target.hide();
325 }
325 }
326 });
326 });
327
327
328 $('#expand_all_files').click(function() {
328 $('#expand_all_files').click(function() {
329 $('.expand_file').each(function() {
329 $('.expand_file').each(function() {
330 var node = $(this);
330 var node = $(this);
331 var icon = $($(node.children().first()).children().first());
331 var icon = $($(node.children().first()).children().first());
332 var id = $(this).attr('fid');
332 var id = $(this).attr('fid');
333 var target = $('#'+id);
333 var target = $('#'+id);
334 var tr = $('#tr_'+id);
334 var tr = $('#tr_'+id);
335 var diff = $('#diff_'+id);
335 var diff = $('#diff_'+id);
336 node.removeClass('expand_file');
336 node.removeClass('expand_file');
337 icon.removeClass('expand_file_icon');
337 icon.removeClass('expand_file_icon');
338 node.addClass('collapse_file');
338 node.addClass('collapse_file');
339 icon.addClass('collapse_file_icon');
339 icon.addClass('collapse_file_icon');
340 diff.show();
340 diff.show();
341 tr.show();
341 tr.show();
342 target.show();
342 target.show();
343 });
343 });
344 });
344 });
345
345
346 $('#collapse_all_files').click(function() {
346 $('#collapse_all_files').click(function() {
347 $('.collapse_file').each(function() {
347 $('.collapse_file').each(function() {
348 var node = $(this);
348 var node = $(this);
349 var icon = $($(node.children().first()).children().first());
349 var icon = $($(node.children().first()).children().first());
350 var id = $(this).attr('fid');
350 var id = $(this).attr('fid');
351 var target = $('#'+id);
351 var target = $('#'+id);
352 var tr = $('#tr_'+id);
352 var tr = $('#tr_'+id);
353 var diff = $('#diff_'+id);
353 var diff = $('#diff_'+id);
354 node.removeClass('collapse_file');
354 node.removeClass('collapse_file');
355 icon.removeClass('collapse_file_icon');
355 icon.removeClass('collapse_file_icon');
356 node.addClass('expand_file');
356 node.addClass('expand_file');
357 icon.addClass('expand_file_icon');
357 icon.addClass('expand_file_icon');
358 diff.hide();
358 diff.hide();
359 tr.hide();
359 tr.hide();
360 target.hide();
360 target.hide();
361 });
361 });
362 });
362 });
363
363
364 // Mouse over behavior for comments and line selection
364 // Mouse over behavior for comments and line selection
365
365
366 // Select the line that comes from the url anchor
366 // Select the line that comes from the url anchor
367 // At the time of development, Chrome didn't seem to support jquery's :target
367 // At the time of development, Chrome didn't seem to support jquery's :target
368 // element, so I had to scroll manually
368 // element, so I had to scroll manually
369 if (location.hash) {
369 if (location.hash) {
370 var splitIx = location.hash.indexOf('/?/');
370 var splitIx = location.hash.indexOf('/?/');
371 if (splitIx !== -1){
371 if (splitIx !== -1){
372 var loc = location.hash.slice(0, splitIx);
372 var loc = location.hash.slice(0, splitIx);
373 var remainder = location.hash.slice(splitIx + 2);
373 var remainder = location.hash.slice(splitIx + 2);
374 }
374 }
375 else{
375 else{
376 var loc = location.hash;
376 var loc = location.hash;
377 var remainder = null;
377 var remainder = null;
378 }
378 }
379 if (loc.length > 1){
379 if (loc.length > 1){
380 var lineno = $(loc+'.lineno');
380 var lineno = $(loc+'.lineno');
381 if (lineno.length > 0){
381 if (lineno.length > 0){
382 var tr = lineno.parents('tr.line');
382 var tr = lineno.parents('tr.line');
383 tr.addClass('selected');
383 tr.addClass('selected');
384
384
385 tr[0].scrollIntoView();
385 tr[0].scrollIntoView();
386
386
387 $.Topic('/ui/plugins/code/anchor_focus').prepare({
387 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
388 tr:tr,
388 tr:tr,
389 remainder:remainder});
389 remainder:remainder});
390 }
390 }
391 }
391 }
392 }
392 }
393
393
394 collapsableContent();
394 collapsableContent();
395 });
395 });
@@ -1,653 +1,653 b''
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
1 // # Copyright (C) 2010-2016 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 // returns a node from given html;
28 // returns a node from given html;
29 var fromHTML = function(html){
29 var fromHTML = function(html){
30 var _html = document.createElement('element');
30 var _html = document.createElement('element');
31 _html.innerHTML = html;
31 _html.innerHTML = html;
32 return _html;
32 return _html;
33 };
33 };
34
34
35 var tableTr = function(cls, body){
35 var tableTr = function(cls, body){
36 var _el = document.createElement('div');
36 var _el = document.createElement('div');
37 var _body = $(body).attr('id');
37 var _body = $(body).attr('id');
38 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
38 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
39 var id = 'comment-tr-{0}'.format(comment_id);
39 var id = 'comment-tr-{0}'.format(comment_id);
40 var _html = ('<table><tbody><tr id="{0}" class="{1}">'+
40 var _html = ('<table><tbody><tr id="{0}" class="{1}">'+
41 '<td class="add-comment-line"><span class="add-comment-content"></span></td>'+
41 '<td class="add-comment-line"><span class="add-comment-content"></span></td>'+
42 '<td></td>'+
42 '<td></td>'+
43 '<td></td>'+
43 '<td></td>'+
44 '<td>{2}</td>'+
44 '<td>{2}</td>'+
45 '</tr></tbody></table>').format(id, cls, body);
45 '</tr></tbody></table>').format(id, cls, body);
46 $(_el).html(_html);
46 $(_el).html(_html);
47 return _el.children[0].children[0].children[0];
47 return _el.children[0].children[0].children[0];
48 };
48 };
49
49
50 var removeInlineForm = function(form) {
50 var removeInlineForm = function(form) {
51 form.parentNode.removeChild(form);
51 form.parentNode.removeChild(form);
52 };
52 };
53
53
54 var createInlineForm = function(parent_tr, f_path, line) {
54 var createInlineForm = function(parent_tr, f_path, line) {
55 var tmpl = $('#comment-inline-form-template').html();
55 var tmpl = $('#comment-inline-form-template').html();
56 tmpl = tmpl.format(f_path, line);
56 tmpl = tmpl.format(f_path, line);
57 var form = tableTr('comment-form-inline', tmpl);
57 var form = tableTr('comment-form-inline', tmpl);
58 var form_hide_button = $(form).find('.hide-inline-form');
58 var form_hide_button = $(form).find('.hide-inline-form');
59
59
60 $(form_hide_button).click(function(e) {
60 $(form_hide_button).click(function(e) {
61 $('.inline-comments').removeClass('hide-comment-button');
61 $('.inline-comments').removeClass('hide-comment-button');
62 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
62 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
63 if ($(newtr.nextElementSibling).hasClass('inline-comments-button')) {
63 if ($(newtr.nextElementSibling).hasClass('inline-comments-button')) {
64 $(newtr.nextElementSibling).show();
64 $(newtr.nextElementSibling).show();
65 }
65 }
66 $(newtr).parents('.comment-form-inline').remove();
66 $(newtr).parents('.comment-form-inline').remove();
67 $(parent_tr).removeClass('form-open');
67 $(parent_tr).removeClass('form-open');
68 $(parent_tr).removeClass('hl-comment');
68 $(parent_tr).removeClass('hl-comment');
69 });
69 });
70
70
71 return form;
71 return form;
72 };
72 };
73
73
74 var getLineNo = function(tr) {
74 var getLineNo = function(tr) {
75 var line;
75 var line;
76 // Try to get the id and return "" (empty string) if it doesn't exist
76 // Try to get the id and return "" (empty string) if it doesn't exist
77 var o = ($(tr).find('.lineno.old').attr('id')||"").split('_');
77 var o = ($(tr).find('.lineno.old').attr('id')||"").split('_');
78 var n = ($(tr).find('.lineno.new').attr('id')||"").split('_');
78 var n = ($(tr).find('.lineno.new').attr('id')||"").split('_');
79 if (n.length >= 2) {
79 if (n.length >= 2) {
80 line = n[n.length-1];
80 line = n[n.length-1];
81 } else if (o.length >= 2) {
81 } else if (o.length >= 2) {
82 line = o[o.length-1];
82 line = o[o.length-1];
83 }
83 }
84 return line;
84 return line;
85 };
85 };
86
86
87 /**
87 /**
88 * make a single inline comment and place it inside
88 * make a single inline comment and place it inside
89 */
89 */
90 var renderInlineComment = function(json_data, show_add_button) {
90 var renderInlineComment = function(json_data, show_add_button) {
91 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
91 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
92 try {
92 try {
93 var html = json_data.rendered_text;
93 var html = json_data.rendered_text;
94 var lineno = json_data.line_no;
94 var lineno = json_data.line_no;
95 var target_id = json_data.target_id;
95 var target_id = json_data.target_id;
96 placeInline(target_id, lineno, html, show_add_button);
96 placeInline(target_id, lineno, html, show_add_button);
97 } catch (e) {
97 } catch (e) {
98 console.error(e);
98 console.error(e);
99 }
99 }
100 };
100 };
101
101
102 function bindDeleteCommentButtons() {
102 function bindDeleteCommentButtons() {
103 $('.delete-comment').one('click', function() {
103 $('.delete-comment').one('click', function() {
104 var comment_id = $(this).data("comment-id");
104 var comment_id = $(this).data("comment-id");
105
105
106 if (comment_id){
106 if (comment_id){
107 deleteComment(comment_id);
107 deleteComment(comment_id);
108 }
108 }
109 });
109 });
110 }
110 }
111
111
112 /**
112 /**
113 * Inject inline comment for on given TR this tr should be always an .line
113 * Inject inline comment for on given TR this tr should be always an .line
114 * tr containing the line. Code will detect comment, and always put the comment
114 * tr containing the line. Code will detect comment, and always put the comment
115 * block at the very bottom
115 * block at the very bottom
116 */
116 */
117 var injectInlineForm = function(tr){
117 var injectInlineForm = function(tr){
118 if (!$(tr).hasClass('line')) {
118 if (!$(tr).hasClass('line')) {
119 return;
119 return;
120 }
120 }
121
121
122 var _td = $(tr).find('.code').get(0);
122 var _td = $(tr).find('.code').get(0);
123 if ($(tr).hasClass('form-open') ||
123 if ($(tr).hasClass('form-open') ||
124 $(tr).hasClass('context') ||
124 $(tr).hasClass('context') ||
125 $(_td).hasClass('no-comment')) {
125 $(_td).hasClass('no-comment')) {
126 return;
126 return;
127 }
127 }
128 $(tr).addClass('form-open');
128 $(tr).addClass('form-open');
129 $(tr).addClass('hl-comment');
129 $(tr).addClass('hl-comment');
130 var node = $(tr.parentNode.parentNode.parentNode).find('.full_f_path').get(0);
130 var node = $(tr.parentNode.parentNode.parentNode).find('.full_f_path').get(0);
131 var f_path = $(node).attr('path');
131 var f_path = $(node).attr('path');
132 var lineno = getLineNo(tr);
132 var lineno = getLineNo(tr);
133 var form = createInlineForm(tr, f_path, lineno);
133 var form = createInlineForm(tr, f_path, lineno);
134
134
135 var parent = tr;
135 var parent = tr;
136 while (1) {
136 while (1) {
137 var n = parent.nextElementSibling;
137 var n = parent.nextElementSibling;
138 // next element are comments !
138 // next element are comments !
139 if ($(n).hasClass('inline-comments')) {
139 if ($(n).hasClass('inline-comments')) {
140 parent = n;
140 parent = n;
141 }
141 }
142 else {
142 else {
143 break;
143 break;
144 }
144 }
145 }
145 }
146 var _parent = $(parent).get(0);
146 var _parent = $(parent).get(0);
147 $(_parent).after(form);
147 $(_parent).after(form);
148 $('.comment-form-inline').prev('.inline-comments').addClass('hide-comment-button');
148 $('.comment-form-inline').prev('.inline-comments').addClass('hide-comment-button');
149 var f = $(form).get(0);
149 var f = $(form).get(0);
150
150
151 var _form = $(f).find('.inline-form').get(0);
151 var _form = $(f).find('.inline-form').get(0);
152
152
153 var pullRequestId = templateContext.pull_request_data.pull_request_id;
153 var pullRequestId = templateContext.pull_request_data.pull_request_id;
154 var commitId = templateContext.commit_data.commit_id;
154 var commitId = templateContext.commit_data.commit_id;
155
155
156 var commentForm = new CommentForm(_form, commitId, pullRequestId, lineno, false);
156 var commentForm = new CommentForm(_form, commitId, pullRequestId, lineno, false);
157 var cm = commentForm.getCmInstance();
157 var cm = commentForm.getCmInstance();
158
158
159 // set a CUSTOM submit handler for inline comments.
159 // set a CUSTOM submit handler for inline comments.
160 commentForm.setHandleFormSubmit(function(o) {
160 commentForm.setHandleFormSubmit(function(o) {
161 var text = commentForm.cm.getValue();
161 var text = commentForm.cm.getValue();
162
162
163 if (text === "") {
163 if (text === "") {
164 return;
164 return;
165 }
165 }
166
166
167 if (lineno === undefined) {
167 if (lineno === undefined) {
168 alert('missing line !');
168 alert('missing line !');
169 return;
169 return;
170 }
170 }
171 if (f_path === undefined) {
171 if (f_path === undefined) {
172 alert('missing file path !');
172 alert('missing file path !');
173 return;
173 return;
174 }
174 }
175
175
176 var excludeCancelBtn = false;
176 var excludeCancelBtn = false;
177 var submitEvent = true;
177 var submitEvent = true;
178 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
178 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
179 commentForm.cm.setOption("readOnly", true);
179 commentForm.cm.setOption("readOnly", true);
180 var postData = {
180 var postData = {
181 'text': text,
181 'text': text,
182 'f_path': f_path,
182 'f_path': f_path,
183 'line': lineno,
183 'line': lineno,
184 'csrf_token': CSRF_TOKEN
184 'csrf_token': CSRF_TOKEN
185 };
185 };
186 var submitSuccessCallback = function(o) {
186 var submitSuccessCallback = function(o) {
187 $(tr).removeClass('form-open');
187 $(tr).removeClass('form-open');
188 removeInlineForm(f);
188 removeInlineForm(f);
189 renderInlineComment(o);
189 renderInlineComment(o);
190 $('.inline-comments').removeClass('hide-comment-button');
190 $('.inline-comments').removeClass('hide-comment-button');
191
191
192 // re trigger the linkification of next/prev navigation
192 // re trigger the linkification of next/prev navigation
193 linkifyComments($('.inline-comment-injected'));
193 linkifyComments($('.inline-comment-injected'));
194 timeagoActivate();
194 timeagoActivate();
195 bindDeleteCommentButtons();
195 bindDeleteCommentButtons();
196 commentForm.setActionButtonsDisabled(false);
196 commentForm.setActionButtonsDisabled(false);
197
197
198 };
198 };
199 var submitFailCallback = function(){
199 var submitFailCallback = function(){
200 commentForm.resetCommentFormState(text)
200 commentForm.resetCommentFormState(text)
201 };
201 };
202 commentForm.submitAjaxPOST(
202 commentForm.submitAjaxPOST(
203 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
203 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
204 });
204 });
205
205
206 setTimeout(function() {
206 setTimeout(function() {
207 // callbacks
207 // callbacks
208 if (cm !== undefined) {
208 if (cm !== undefined) {
209 cm.focus();
209 cm.focus();
210 }
210 }
211 }, 10);
211 }, 10);
212
212
213 $.Topic('/ui/plugins/code/comment_form_built').prepare({
213 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
214 form:_form,
214 form:_form,
215 parent:_parent}
215 parent:_parent}
216 );
216 );
217 };
217 };
218
218
219 var deleteComment = function(comment_id) {
219 var deleteComment = function(comment_id) {
220 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
220 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
221 var postData = {
221 var postData = {
222 '_method': 'delete',
222 '_method': 'delete',
223 'csrf_token': CSRF_TOKEN
223 'csrf_token': CSRF_TOKEN
224 };
224 };
225
225
226 var success = function(o) {
226 var success = function(o) {
227 window.location.reload();
227 window.location.reload();
228 };
228 };
229 ajaxPOST(url, postData, success);
229 ajaxPOST(url, postData, success);
230 };
230 };
231
231
232 var createInlineAddButton = function(tr){
232 var createInlineAddButton = function(tr){
233 var label = _gettext('Add another comment');
233 var label = _gettext('Add another comment');
234 var html_el = document.createElement('div');
234 var html_el = document.createElement('div');
235 $(html_el).addClass('add-comment');
235 $(html_el).addClass('add-comment');
236 html_el.innerHTML = '<span class="btn btn-secondary">{0}</span>'.format(label);
236 html_el.innerHTML = '<span class="btn btn-secondary">{0}</span>'.format(label);
237 var add = new $(html_el);
237 var add = new $(html_el);
238 add.on('click', function(e) {
238 add.on('click', function(e) {
239 injectInlineForm(tr);
239 injectInlineForm(tr);
240 });
240 });
241 return add;
241 return add;
242 };
242 };
243
243
244 var placeAddButton = function(target_tr){
244 var placeAddButton = function(target_tr){
245 if(!target_tr){
245 if(!target_tr){
246 return;
246 return;
247 }
247 }
248 var last_node = target_tr;
248 var last_node = target_tr;
249 // scan
249 // scan
250 while (1){
250 while (1){
251 var n = last_node.nextElementSibling;
251 var n = last_node.nextElementSibling;
252 // next element are comments !
252 // next element are comments !
253 if($(n).hasClass('inline-comments')){
253 if($(n).hasClass('inline-comments')){
254 last_node = n;
254 last_node = n;
255 // also remove the comment button from previous
255 // also remove the comment button from previous
256 var comment_add_buttons = $(last_node).find('.add-comment');
256 var comment_add_buttons = $(last_node).find('.add-comment');
257 for(var i=0; i<comment_add_buttons.length; i++){
257 for(var i=0; i<comment_add_buttons.length; i++){
258 var b = comment_add_buttons[i];
258 var b = comment_add_buttons[i];
259 b.parentNode.removeChild(b);
259 b.parentNode.removeChild(b);
260 }
260 }
261 }
261 }
262 else{
262 else{
263 break;
263 break;
264 }
264 }
265 }
265 }
266 var add = createInlineAddButton(target_tr);
266 var add = createInlineAddButton(target_tr);
267 // get the comment div
267 // get the comment div
268 var comment_block = $(last_node).find('.comment')[0];
268 var comment_block = $(last_node).find('.comment')[0];
269 // attach add button
269 // attach add button
270 $(add).insertAfter(comment_block);
270 $(add).insertAfter(comment_block);
271 };
271 };
272
272
273 /**
273 /**
274 * Places the inline comment into the changeset block in proper line position
274 * Places the inline comment into the changeset block in proper line position
275 */
275 */
276 var placeInline = function(target_container, lineno, html, show_add_button) {
276 var placeInline = function(target_container, lineno, html, show_add_button) {
277 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
277 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
278
278
279 var lineid = "{0}_{1}".format(target_container, lineno);
279 var lineid = "{0}_{1}".format(target_container, lineno);
280 var target_line = $('#' + lineid).get(0);
280 var target_line = $('#' + lineid).get(0);
281 var comment = new $(tableTr('inline-comments', html));
281 var comment = new $(tableTr('inline-comments', html));
282 // check if there are comments already !
282 // check if there are comments already !
283 var parent_node = target_line.parentNode;
283 var parent_node = target_line.parentNode;
284 var root_parent = parent_node;
284 var root_parent = parent_node;
285 while (1) {
285 while (1) {
286 var n = parent_node.nextElementSibling;
286 var n = parent_node.nextElementSibling;
287 // next element are comments !
287 // next element are comments !
288 if ($(n).hasClass('inline-comments')) {
288 if ($(n).hasClass('inline-comments')) {
289 parent_node = n;
289 parent_node = n;
290 }
290 }
291 else {
291 else {
292 break;
292 break;
293 }
293 }
294 }
294 }
295 // put in the comment at the bottom
295 // put in the comment at the bottom
296 $(comment).insertAfter(parent_node);
296 $(comment).insertAfter(parent_node);
297 $(comment).find('.comment-inline').addClass('inline-comment-injected');
297 $(comment).find('.comment-inline').addClass('inline-comment-injected');
298 // scan nodes, and attach add button to last one
298 // scan nodes, and attach add button to last one
299 if (show_add_button) {
299 if (show_add_button) {
300 placeAddButton(root_parent);
300 placeAddButton(root_parent);
301 }
301 }
302
302
303 return target_line;
303 return target_line;
304 };
304 };
305
305
306 var linkifyComments = function(comments) {
306 var linkifyComments = function(comments) {
307
307
308 for (var i = 0; i < comments.length; i++) {
308 for (var i = 0; i < comments.length; i++) {
309 var comment_id = $(comments[i]).data('comment-id');
309 var comment_id = $(comments[i]).data('comment-id');
310 var prev_comment_id = $(comments[i - 1]).data('comment-id');
310 var prev_comment_id = $(comments[i - 1]).data('comment-id');
311 var next_comment_id = $(comments[i + 1]).data('comment-id');
311 var next_comment_id = $(comments[i + 1]).data('comment-id');
312
312
313 // place next/prev links
313 // place next/prev links
314 if (prev_comment_id) {
314 if (prev_comment_id) {
315 $('#prev_c_' + comment_id).show();
315 $('#prev_c_' + comment_id).show();
316 $('#prev_c_' + comment_id + " a.arrow_comment_link").attr(
316 $('#prev_c_' + comment_id + " a.arrow_comment_link").attr(
317 'href', '#comment-' + prev_comment_id).removeClass('disabled');
317 'href', '#comment-' + prev_comment_id).removeClass('disabled');
318 }
318 }
319 if (next_comment_id) {
319 if (next_comment_id) {
320 $('#next_c_' + comment_id).show();
320 $('#next_c_' + comment_id).show();
321 $('#next_c_' + comment_id + " a.arrow_comment_link").attr(
321 $('#next_c_' + comment_id + " a.arrow_comment_link").attr(
322 'href', '#comment-' + next_comment_id).removeClass('disabled');
322 'href', '#comment-' + next_comment_id).removeClass('disabled');
323 }
323 }
324 // place a first link to the total counter
324 // place a first link to the total counter
325 if (i === 0) {
325 if (i === 0) {
326 $('#inline-comments-counter').attr('href', '#comment-' + comment_id);
326 $('#inline-comments-counter').attr('href', '#comment-' + comment_id);
327 }
327 }
328 }
328 }
329
329
330 };
330 };
331
331
332 /**
332 /**
333 * Iterates over all the inlines, and places them inside proper blocks of data
333 * Iterates over all the inlines, and places them inside proper blocks of data
334 */
334 */
335 var renderInlineComments = function(file_comments, show_add_button) {
335 var renderInlineComments = function(file_comments, show_add_button) {
336 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
336 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
337
337
338 for (var i = 0; i < file_comments.length; i++) {
338 for (var i = 0; i < file_comments.length; i++) {
339 var box = file_comments[i];
339 var box = file_comments[i];
340
340
341 var target_id = $(box).attr('target_id');
341 var target_id = $(box).attr('target_id');
342
342
343 // actually comments with line numbers
343 // actually comments with line numbers
344 var comments = box.children;
344 var comments = box.children;
345
345
346 for (var j = 0; j < comments.length; j++) {
346 for (var j = 0; j < comments.length; j++) {
347 var data = {
347 var data = {
348 'rendered_text': comments[j].outerHTML,
348 'rendered_text': comments[j].outerHTML,
349 'line_no': $(comments[j]).attr('line'),
349 'line_no': $(comments[j]).attr('line'),
350 'target_id': target_id
350 'target_id': target_id
351 };
351 };
352 renderInlineComment(data, show_add_button);
352 renderInlineComment(data, show_add_button);
353 }
353 }
354 }
354 }
355
355
356 // since order of injection is random, we're now re-iterating
356 // since order of injection is random, we're now re-iterating
357 // from correct order and filling in links
357 // from correct order and filling in links
358 linkifyComments($('.inline-comment-injected'));
358 linkifyComments($('.inline-comment-injected'));
359 bindDeleteCommentButtons();
359 bindDeleteCommentButtons();
360 firefoxAnchorFix();
360 firefoxAnchorFix();
361 };
361 };
362
362
363
363
364 /* Comment form for main and inline comments */
364 /* Comment form for main and inline comments */
365 var CommentForm = (function() {
365 var CommentForm = (function() {
366 "use strict";
366 "use strict";
367
367
368 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions) {
368 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions) {
369
369
370 this.withLineNo = function(selector) {
370 this.withLineNo = function(selector) {
371 var lineNo = this.lineNo;
371 var lineNo = this.lineNo;
372 if (lineNo === undefined) {
372 if (lineNo === undefined) {
373 return selector
373 return selector
374 } else {
374 } else {
375 return selector + '_' + lineNo;
375 return selector + '_' + lineNo;
376 }
376 }
377 };
377 };
378
378
379 this.commitId = commitId;
379 this.commitId = commitId;
380 this.pullRequestId = pullRequestId;
380 this.pullRequestId = pullRequestId;
381 this.lineNo = lineNo;
381 this.lineNo = lineNo;
382 this.initAutocompleteActions = initAutocompleteActions;
382 this.initAutocompleteActions = initAutocompleteActions;
383
383
384 this.previewButton = this.withLineNo('#preview-btn');
384 this.previewButton = this.withLineNo('#preview-btn');
385 this.previewContainer = this.withLineNo('#preview-container');
385 this.previewContainer = this.withLineNo('#preview-container');
386
386
387 this.previewBoxSelector = this.withLineNo('#preview-box');
387 this.previewBoxSelector = this.withLineNo('#preview-box');
388
388
389 this.editButton = this.withLineNo('#edit-btn');
389 this.editButton = this.withLineNo('#edit-btn');
390 this.editContainer = this.withLineNo('#edit-container');
390 this.editContainer = this.withLineNo('#edit-container');
391
391
392 this.cancelButton = this.withLineNo('#cancel-btn');
392 this.cancelButton = this.withLineNo('#cancel-btn');
393
393
394 this.statusChange = '#change_status';
394 this.statusChange = '#change_status';
395 this.cmBox = this.withLineNo('#text');
395 this.cmBox = this.withLineNo('#text');
396 this.cm = initCommentBoxCodeMirror(this.cmBox, this.initAutocompleteActions);
396 this.cm = initCommentBoxCodeMirror(this.cmBox, this.initAutocompleteActions);
397
397
398 this.submitForm = formElement;
398 this.submitForm = formElement;
399 this.submitButton = $(this.submitForm).find('input[type="submit"]');
399 this.submitButton = $(this.submitForm).find('input[type="submit"]');
400 this.submitButtonText = this.submitButton.val();
400 this.submitButtonText = this.submitButton.val();
401
401
402 this.previewUrl = pyroutes.url('changeset_comment_preview',
402 this.previewUrl = pyroutes.url('changeset_comment_preview',
403 {'repo_name': templateContext.repo_name});
403 {'repo_name': templateContext.repo_name});
404
404
405 // based on commitId, or pullReuqestId decide where do we submit
405 // based on commitId, or pullReuqestId decide where do we submit
406 // out data
406 // out data
407 if (this.commitId){
407 if (this.commitId){
408 this.submitUrl = pyroutes.url('changeset_comment',
408 this.submitUrl = pyroutes.url('changeset_comment',
409 {'repo_name': templateContext.repo_name,
409 {'repo_name': templateContext.repo_name,
410 'revision': this.commitId});
410 'revision': this.commitId});
411
411
412 } else if (this.pullRequestId) {
412 } else if (this.pullRequestId) {
413 this.submitUrl = pyroutes.url('pullrequest_comment',
413 this.submitUrl = pyroutes.url('pullrequest_comment',
414 {'repo_name': templateContext.repo_name,
414 {'repo_name': templateContext.repo_name,
415 'pull_request_id': this.pullRequestId});
415 'pull_request_id': this.pullRequestId});
416
416
417 } else {
417 } else {
418 throw new Error(
418 throw new Error(
419 'CommentForm requires pullRequestId, or commitId to be specified.')
419 'CommentForm requires pullRequestId, or commitId to be specified.')
420 }
420 }
421
421
422 this.getCmInstance = function(){
422 this.getCmInstance = function(){
423 return this.cm
423 return this.cm
424 };
424 };
425
425
426 var self = this;
426 var self = this;
427
427
428 this.getCommentStatus = function() {
428 this.getCommentStatus = function() {
429 return $(this.submitForm).find(this.statusChange).val();
429 return $(this.submitForm).find(this.statusChange).val();
430 };
430 };
431
431
432 this.isAllowedToSubmit = function() {
432 this.isAllowedToSubmit = function() {
433 return !$(this.submitButton).prop('disabled');
433 return !$(this.submitButton).prop('disabled');
434 };
434 };
435
435
436 this.initStatusChangeSelector = function(){
436 this.initStatusChangeSelector = function(){
437 var formatChangeStatus = function(state, escapeMarkup) {
437 var formatChangeStatus = function(state, escapeMarkup) {
438 var originalOption = state.element;
438 var originalOption = state.element;
439 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
439 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
440 '<span>' + escapeMarkup(state.text) + '</span>';
440 '<span>' + escapeMarkup(state.text) + '</span>';
441 };
441 };
442 var formatResult = function(result, container, query, escapeMarkup) {
442 var formatResult = function(result, container, query, escapeMarkup) {
443 return formatChangeStatus(result, escapeMarkup);
443 return formatChangeStatus(result, escapeMarkup);
444 };
444 };
445
445
446 var formatSelection = function(data, container, escapeMarkup) {
446 var formatSelection = function(data, container, escapeMarkup) {
447 return formatChangeStatus(data, escapeMarkup);
447 return formatChangeStatus(data, escapeMarkup);
448 };
448 };
449
449
450 $(this.submitForm).find(this.statusChange).select2({
450 $(this.submitForm).find(this.statusChange).select2({
451 placeholder: _gettext('Status Review'),
451 placeholder: _gettext('Status Review'),
452 formatResult: formatResult,
452 formatResult: formatResult,
453 formatSelection: formatSelection,
453 formatSelection: formatSelection,
454 containerCssClass: "drop-menu status_box_menu",
454 containerCssClass: "drop-menu status_box_menu",
455 dropdownCssClass: "drop-menu-dropdown",
455 dropdownCssClass: "drop-menu-dropdown",
456 dropdownAutoWidth: true,
456 dropdownAutoWidth: true,
457 minimumResultsForSearch: -1
457 minimumResultsForSearch: -1
458 });
458 });
459 $(this.submitForm).find(this.statusChange).on('change', function() {
459 $(this.submitForm).find(this.statusChange).on('change', function() {
460 var status = self.getCommentStatus();
460 var status = self.getCommentStatus();
461 if (status && !self.lineNo) {
461 if (status && !self.lineNo) {
462 $(self.submitButton).prop('disabled', false);
462 $(self.submitButton).prop('disabled', false);
463 }
463 }
464 //todo, fix this name
464 //todo, fix this name
465 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
465 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
466 self.cm.setOption('placeholder', placeholderText);
466 self.cm.setOption('placeholder', placeholderText);
467 })
467 })
468 };
468 };
469
469
470 // reset the comment form into it's original state
470 // reset the comment form into it's original state
471 this.resetCommentFormState = function(content) {
471 this.resetCommentFormState = function(content) {
472 content = content || '';
472 content = content || '';
473
473
474 $(this.editContainer).show();
474 $(this.editContainer).show();
475 $(this.editButton).hide();
475 $(this.editButton).hide();
476
476
477 $(this.previewContainer).hide();
477 $(this.previewContainer).hide();
478 $(this.previewButton).show();
478 $(this.previewButton).show();
479
479
480 this.setActionButtonsDisabled(true);
480 this.setActionButtonsDisabled(true);
481 self.cm.setValue(content);
481 self.cm.setValue(content);
482 self.cm.setOption("readOnly", false);
482 self.cm.setOption("readOnly", false);
483 };
483 };
484
484
485 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
485 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
486 failHandler = failHandler || function() {};
486 failHandler = failHandler || function() {};
487 var postData = toQueryString(postData);
487 var postData = toQueryString(postData);
488 var request = $.ajax({
488 var request = $.ajax({
489 url: url,
489 url: url,
490 type: 'POST',
490 type: 'POST',
491 data: postData,
491 data: postData,
492 headers: {'X-PARTIAL-XHR': true}
492 headers: {'X-PARTIAL-XHR': true}
493 })
493 })
494 .done(function(data) {
494 .done(function(data) {
495 successHandler(data);
495 successHandler(data);
496 })
496 })
497 .fail(function(data, textStatus, errorThrown){
497 .fail(function(data, textStatus, errorThrown){
498 alert(
498 alert(
499 "Error while submitting comment.\n" +
499 "Error while submitting comment.\n" +
500 "Error code {0} ({1}).".format(data.status, data.statusText));
500 "Error code {0} ({1}).".format(data.status, data.statusText));
501 failHandler()
501 failHandler()
502 });
502 });
503 return request;
503 return request;
504 };
504 };
505
505
506 // overwrite a submitHandler, we need to do it for inline comments
506 // overwrite a submitHandler, we need to do it for inline comments
507 this.setHandleFormSubmit = function(callback) {
507 this.setHandleFormSubmit = function(callback) {
508 this.handleFormSubmit = callback;
508 this.handleFormSubmit = callback;
509 };
509 };
510
510
511 // default handler for for submit for main comments
511 // default handler for for submit for main comments
512 this.handleFormSubmit = function() {
512 this.handleFormSubmit = function() {
513 var text = self.cm.getValue();
513 var text = self.cm.getValue();
514 var status = self.getCommentStatus();
514 var status = self.getCommentStatus();
515
515
516 if (text === "" && !status) {
516 if (text === "" && !status) {
517 return;
517 return;
518 }
518 }
519
519
520 var excludeCancelBtn = false;
520 var excludeCancelBtn = false;
521 var submitEvent = true;
521 var submitEvent = true;
522 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
522 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
523 self.cm.setOption("readOnly", true);
523 self.cm.setOption("readOnly", true);
524 var postData = {
524 var postData = {
525 'text': text,
525 'text': text,
526 'changeset_status': status,
526 'changeset_status': status,
527 'csrf_token': CSRF_TOKEN
527 'csrf_token': CSRF_TOKEN
528 };
528 };
529
529
530 var submitSuccessCallback = function(o) {
530 var submitSuccessCallback = function(o) {
531 if (status) {
531 if (status) {
532 location.reload(true);
532 location.reload(true);
533 } else {
533 } else {
534 $('#injected_page_comments').append(o.rendered_text);
534 $('#injected_page_comments').append(o.rendered_text);
535 self.resetCommentFormState();
535 self.resetCommentFormState();
536 bindDeleteCommentButtons();
536 bindDeleteCommentButtons();
537 timeagoActivate();
537 timeagoActivate();
538 }
538 }
539 };
539 };
540 var submitFailCallback = function(){
540 var submitFailCallback = function(){
541 self.resetCommentFormState(text)
541 self.resetCommentFormState(text)
542 };
542 };
543 self.submitAjaxPOST(
543 self.submitAjaxPOST(
544 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
544 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
545 };
545 };
546
546
547 this.previewSuccessCallback = function(o) {
547 this.previewSuccessCallback = function(o) {
548 $(self.previewBoxSelector).html(o);
548 $(self.previewBoxSelector).html(o);
549 $(self.previewBoxSelector).removeClass('unloaded');
549 $(self.previewBoxSelector).removeClass('unloaded');
550
550
551 // swap buttons
551 // swap buttons
552 $(self.previewButton).hide();
552 $(self.previewButton).hide();
553 $(self.editButton).show();
553 $(self.editButton).show();
554
554
555 // unlock buttons
555 // unlock buttons
556 self.setActionButtonsDisabled(false);
556 self.setActionButtonsDisabled(false);
557 };
557 };
558
558
559 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
559 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
560 excludeCancelBtn = excludeCancelBtn || false;
560 excludeCancelBtn = excludeCancelBtn || false;
561 submitEvent = submitEvent || false;
561 submitEvent = submitEvent || false;
562
562
563 $(this.editButton).prop('disabled', state);
563 $(this.editButton).prop('disabled', state);
564 $(this.previewButton).prop('disabled', state);
564 $(this.previewButton).prop('disabled', state);
565
565
566 if (!excludeCancelBtn) {
566 if (!excludeCancelBtn) {
567 $(this.cancelButton).prop('disabled', state);
567 $(this.cancelButton).prop('disabled', state);
568 }
568 }
569
569
570 var submitState = state;
570 var submitState = state;
571 if (!submitEvent && this.getCommentStatus() && !this.lineNo) {
571 if (!submitEvent && this.getCommentStatus() && !this.lineNo) {
572 // if the value of commit review status is set, we allow
572 // if the value of commit review status is set, we allow
573 // submit button, but only on Main form, lineNo means inline
573 // submit button, but only on Main form, lineNo means inline
574 submitState = false
574 submitState = false
575 }
575 }
576 $(this.submitButton).prop('disabled', submitState);
576 $(this.submitButton).prop('disabled', submitState);
577 if (submitEvent) {
577 if (submitEvent) {
578 $(this.submitButton).val(_gettext('Submitting...'));
578 $(this.submitButton).val(_gettext('Submitting...'));
579 } else {
579 } else {
580 $(this.submitButton).val(this.submitButtonText);
580 $(this.submitButton).val(this.submitButtonText);
581 }
581 }
582
582
583 };
583 };
584
584
585 // lock preview/edit/submit buttons on load, but exclude cancel button
585 // lock preview/edit/submit buttons on load, but exclude cancel button
586 var excludeCancelBtn = true;
586 var excludeCancelBtn = true;
587 this.setActionButtonsDisabled(true, excludeCancelBtn);
587 this.setActionButtonsDisabled(true, excludeCancelBtn);
588
588
589 // anonymous users don't have access to initialized CM instance
589 // anonymous users don't have access to initialized CM instance
590 if (this.cm !== undefined){
590 if (this.cm !== undefined){
591 this.cm.on('change', function(cMirror) {
591 this.cm.on('change', function(cMirror) {
592 if (cMirror.getValue() === "") {
592 if (cMirror.getValue() === "") {
593 self.setActionButtonsDisabled(true, excludeCancelBtn)
593 self.setActionButtonsDisabled(true, excludeCancelBtn)
594 } else {
594 } else {
595 self.setActionButtonsDisabled(false, excludeCancelBtn)
595 self.setActionButtonsDisabled(false, excludeCancelBtn)
596 }
596 }
597 });
597 });
598 }
598 }
599
599
600 $(this.editButton).on('click', function(e) {
600 $(this.editButton).on('click', function(e) {
601 e.preventDefault();
601 e.preventDefault();
602
602
603 $(self.previewButton).show();
603 $(self.previewButton).show();
604 $(self.previewContainer).hide();
604 $(self.previewContainer).hide();
605 $(self.editButton).hide();
605 $(self.editButton).hide();
606 $(self.editContainer).show();
606 $(self.editContainer).show();
607
607
608 });
608 });
609
609
610 $(this.previewButton).on('click', function(e) {
610 $(this.previewButton).on('click', function(e) {
611 e.preventDefault();
611 e.preventDefault();
612 var text = self.cm.getValue();
612 var text = self.cm.getValue();
613
613
614 if (text === "") {
614 if (text === "") {
615 return;
615 return;
616 }
616 }
617
617
618 var postData = {
618 var postData = {
619 'text': text,
619 'text': text,
620 'renderer': DEFAULT_RENDERER,
620 'renderer': DEFAULT_RENDERER,
621 'csrf_token': CSRF_TOKEN
621 'csrf_token': CSRF_TOKEN
622 };
622 };
623
623
624 // lock ALL buttons on preview
624 // lock ALL buttons on preview
625 self.setActionButtonsDisabled(true);
625 self.setActionButtonsDisabled(true);
626
626
627 $(self.previewBoxSelector).addClass('unloaded');
627 $(self.previewBoxSelector).addClass('unloaded');
628 $(self.previewBoxSelector).html(_gettext('Loading ...'));
628 $(self.previewBoxSelector).html(_gettext('Loading ...'));
629 $(self.editContainer).hide();
629 $(self.editContainer).hide();
630 $(self.previewContainer).show();
630 $(self.previewContainer).show();
631
631
632 // by default we reset state of comment preserving the text
632 // by default we reset state of comment preserving the text
633 var previewFailCallback = function(){
633 var previewFailCallback = function(){
634 self.resetCommentFormState(text)
634 self.resetCommentFormState(text)
635 };
635 };
636 self.submitAjaxPOST(
636 self.submitAjaxPOST(
637 self.previewUrl, postData, self.previewSuccessCallback, previewFailCallback);
637 self.previewUrl, postData, self.previewSuccessCallback, previewFailCallback);
638
638
639 });
639 });
640
640
641 $(this.submitForm).submit(function(e) {
641 $(this.submitForm).submit(function(e) {
642 e.preventDefault();
642 e.preventDefault();
643 var allowedToSubmit = self.isAllowedToSubmit();
643 var allowedToSubmit = self.isAllowedToSubmit();
644 if (!allowedToSubmit){
644 if (!allowedToSubmit){
645 return false;
645 return false;
646 }
646 }
647 self.handleFormSubmit();
647 self.handleFormSubmit();
648 });
648 });
649
649
650 }
650 }
651
651
652 return CommentForm;
652 return CommentForm;
653 })();
653 })();
@@ -1,50 +1,59 b''
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
1 // # Copyright (C) 2010-2016 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 var topics = {};
20 var topics = {};
21 jQuery.Topic = function (id) {
21 jQuery.Topic = function (id) {
22 var callbacks, method,
22 var callbacks, method,
23 topic = id && topics[id];
23 topic = id && topics[id];
24
24
25 if (!topic) {
25 if (!topic) {
26 callbacks = jQuery.Callbacks();
26 callbacks = jQuery.Callbacks();
27 topic = {
27 topic = {
28 unhandledData: [],
28 unhandledData: [],
29 publish: callbacks.fire,
29 publish: callbacks.fire,
30 prepare: function(){
30 prepare: function(){
31 for(var i=0; i< arguments.length; i++){
31 for(var i=0; i< arguments.length; i++){
32 this.unhandledData.push(arguments[i]);
32 this.unhandledData.push(arguments[i]);
33 }
33 }
34 },
34 },
35 prepareOrPublish: function(){
36 if (callbacks.has() === true){
37 this.publish.apply(this, arguments);
38 }
39 else{
40 this.prepare.apply(this, arguments);
41 }
42 },
35 processPrepared: function(){
43 processPrepared: function(){
36 var data = this.unhandledData;
44 var data = this.unhandledData;
37 this.unhandledData = [];
45 this.unhandledData = [];
38 for(var i=0; i< data.length; i++){
46 for(var i=0; i< data.length; i++){
39 this.publish(data[i]);
47 this.publish(data[i]);
40 }
48 }
41 },
49 },
42 subscribe: callbacks.add,
50 subscribe: callbacks.add,
43 unsubscribe: callbacks.remove
51 unsubscribe: callbacks.remove,
52 callbacks: callbacks
44 };
53 };
45 if (id) {
54 if (id) {
46 topics[id] = topic;
55 topics[id] = topic;
47 }
56 }
48 }
57 }
49 return topic;
58 return topic;
50 };
59 };
@@ -1,14 +1,14 b''
1 <%
1 <%
2 from pyramid.renderers import render as pyramid_render
2 from pyramid.renderers import render as pyramid_render
3 from pyramid.threadlocal import get_current_registry, get_current_request
3 from pyramid.threadlocal import get_current_registry, get_current_request
4 pyramid_registry = get_current_registry()
4 pyramid_registry = get_current_registry()
5 %>
5 %>
6 % for plugin, config in pyramid_registry.rhodecode_plugins.items():
6 % for plugin, config in pyramid_registry.rhodecode_plugins.items():
7 % if config['template_hooks'].get('plugin_init_template'):
7 % if config['template_hooks'].get('plugin_init_template'):
8 ${pyramid_render(config['template_hooks'].get('plugin_init_template'),
8 ${pyramid_render(config['template_hooks'].get('plugin_init_template'),
9 {'config':config}, request=get_current_request(), package='rc_ae')|n}
9 {'config':config}, request=get_current_request(), package='rc_ae')|n}
10 % endif
10 % endif
11 % endfor
11 % endfor
12 <script>
12 <script>
13 $.Topic('/plugins/__REGISTER__').publish({});
13 $.Topic('/plugins/__REGISTER__').prepareOrPublish({});
14 </script>
14 </script>
General Comments 0
You need to be logged in to leave comments. Login now