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