##// END OF EJS Templates
js: tooltip allow full html mode using a base64 encoded data property.
marcink -
r4031:8ddabfbf default
parent child Browse files
Show More
@@ -1,656 +1,661 b''
1 1 // # Copyright (C) 2010-2019 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('repo_compare', 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 container.show();
80 80 if (!container.hasClass('loaded')) {
81 81 $.ajax({url: url})
82 82 .complete(function (data) {
83 83 var responseJSON = data.responseJSON;
84 84 container.addClass('loaded');
85 85 container.html(responseJSON.size);
86 86 callback(responseJSON.code_stats)
87 87 })
88 88 .fail(function (data) {
89 89 console.log('failed to load repo stats');
90 90 });
91 91 }
92 92
93 93 };
94 94
95 95 var showRepoStats = function(target, data){
96 96 var container = $('#' + target);
97 97
98 98 if (container.hasClass('loaded')) {
99 99 return
100 100 }
101 101
102 102 var total = 0;
103 103 var no_data = true;
104 104 var tbl = document.createElement('table');
105 105 tbl.setAttribute('class', 'trending_language_tbl rctable');
106 106
107 107 $.each(data, function(key, val){
108 108 total += val.count;
109 109 });
110 110
111 111 var sortedStats = [];
112 112 for (var obj in data){
113 113 sortedStats.push([obj, data[obj]])
114 114 }
115 115 var sortedData = sortedStats.sort(function (a, b) {
116 116 return b[1].count - a[1].count
117 117 });
118 118 var cnt = 0;
119 119 $.each(sortedData, function(idx, val){
120 120 cnt += 1;
121 121 no_data = false;
122 122
123 123 var tr = document.createElement('tr');
124 124
125 125 var key = val[0];
126 126 var obj = {"desc": val[1].desc, "count": val[1].count};
127 127
128 128 // meta language names
129 129 var td1 = document.createElement('td');
130 130 var trending_language_label = document.createElement('div');
131 131 trending_language_label.innerHTML = obj.desc;
132 132 td1.appendChild(trending_language_label);
133 133
134 134 // extensions
135 135 var td2 = document.createElement('td');
136 136 var extension = document.createElement('div');
137 137 extension.innerHTML = ".{0}".format(key)
138 138 td2.appendChild(extension);
139 139
140 140 // number of files
141 141 var td3 = document.createElement('td');
142 142 var file_count = document.createElement('div');
143 143 var percentage_num = Math.round((obj.count / total * 100), 2);
144 144 var label = _ngettext('file', 'files', obj.count);
145 145 file_count.innerHTML = "{0} {1} ({2}%)".format(obj.count, label, percentage_num) ;
146 146 td3.appendChild(file_count);
147 147
148 148 // percentage
149 149 var td4 = document.createElement('td');
150 150 td4.setAttribute("class", 'trending_language');
151 151
152 152 var percentage = document.createElement('div');
153 153 percentage.setAttribute('class', 'lang-bar');
154 154 percentage.innerHTML = "&nbsp;";
155 155 percentage.style.width = percentage_num + '%';
156 156 td4.appendChild(percentage);
157 157
158 158 tr.appendChild(td1);
159 159 tr.appendChild(td2);
160 160 tr.appendChild(td3);
161 161 tr.appendChild(td4);
162 162 tbl.appendChild(tr);
163 163
164 164 });
165 165
166 166 $(container).html(tbl);
167 167 $(container).addClass('loaded');
168 168
169 169 $('#code_stats_show_more').on('click', function (e) {
170 170 e.preventDefault();
171 171 $('.stats_hidden').each(function (idx) {
172 172 $(this).css("display", "");
173 173 });
174 174 $('#code_stats_show_more').hide();
175 175 });
176 176
177 177 };
178 178
179 179 // returns a node from given html;
180 180 var fromHTML = function(html){
181 181 var _html = document.createElement('element');
182 182 _html.innerHTML = html;
183 183 return _html;
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
208 208 var clipboardActivate = function() {
209 209 /*
210 210 *
211 211 * <i class="tooltip icon-plus clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i>
212 212 * */
213 213 var clipboard = new ClipboardJS('.clipboard-action');
214 214
215 215 clipboard.on('success', function(e) {
216 216 var callback = function () {
217 217 $(e.trigger).animate({'opacity': 1.00}, 200)
218 218 };
219 219 $(e.trigger).animate({'opacity': 0.15}, 200, callback);
220 220 e.clearSelection();
221 221 });
222 222 };
223 223
224 224 var tooltipActivate = function () {
225 225 var delay = 50;
226 226 var animation = 'fade';
227 227 var theme = 'tooltipster-shadow';
228 228 var debug = false;
229 229
230 230 $('.tooltip').tooltipster({
231 231 debug: debug,
232 232 theme: theme,
233 233 animation: animation,
234 234 delay: delay,
235 235 contentCloning: true,
236 236 contentAsHTML: true,
237 237
238 238 functionBefore: function (instance, helper) {
239 239 var $origin = $(helper.origin);
240 240 var data = '<div style="white-space: pre-wrap">{0}</div>'.format(instance.content());
241 241 instance.content(data);
242 242 }
243 243 });
244 244 var hovercardCache = {};
245 245
246 246 var loadHoverCard = function (url, callback) {
247 247 var id = url;
248 248
249 249 if (hovercardCache[id] !== undefined) {
250 250 callback(hovercardCache[id]);
251 251 return;
252 252 }
253 253
254 254 hovercardCache[id] = undefined;
255 255 $.get(url, function (data) {
256 256 hovercardCache[id] = data;
257 257 callback(hovercardCache[id]);
258 258 }).fail(function (data, textStatus, errorThrown) {
259 259 var msg = "Error while fetching hovercard.\nError code {0} ({1}).".format(data.status,data.statusText);
260 260 callback(msg);
261 261 });
262 262 };
263 263
264 264 $('.tooltip-hovercard').tooltipster({
265 265 debug: debug,
266 266 theme: theme,
267 267 animation: animation,
268 268 delay: delay,
269 269 interactive: true,
270 270 contentCloning: true,
271 271
272 272 trigger: 'custom',
273 273 triggerOpen: {
274 274 mouseenter: true,
275 275 },
276 276 triggerClose: {
277 277 mouseleave: true,
278 278 originClick: true,
279 279 touchleave: true
280 280 },
281 281 content: _gettext('Loading...'),
282 282 contentAsHTML: true,
283 283 updateAnimation: null,
284 284
285 285 functionBefore: function (instance, helper) {
286 286
287 287 var $origin = $(helper.origin);
288 288
289 289 // we set a variable so the data is only loaded once via Ajax, not every time the tooltip opens
290 290 if ($origin.data('loaded') !== true) {
291 291 var hovercardUrl = $origin.data('hovercardUrl');
292 292
293 293 if (hovercardUrl !== undefined && hovercardUrl !== "") {
294 294 loadHoverCard(hovercardUrl, function (data) {
295 295 instance.content(data);
296 296 })
297 297 } else {
298 var data = '<div style="white-space: pre-wrap">{0}</div>'.format($origin.data('hovercardAlt'))
298 if ($origin.data('hovercardAltHtml')) {
299 var data = atob($origin.data('hovercardAltHtml'));
300 } else {
301 var data = '<div style="white-space: pre-wrap">{0}</div>'.format($origin.data('hovercardAlt'))
302 }
303
299 304 instance.content(data);
300 305 }
301 306
302 307 // to remember that the data has been loaded
303 308 $origin.data('loaded', true);
304 309 }
305 310 }
306 311 })
307 312 };
308 313
309 314 // Formatting values in a Select2 dropdown of commit references
310 315 var formatSelect2SelectionRefs = function(commit_ref){
311 316 var tmpl = '';
312 317 if (!commit_ref.text || commit_ref.type === 'sha'){
313 318 return commit_ref.text;
314 319 }
315 320 if (commit_ref.type === 'branch'){
316 321 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
317 322 } else if (commit_ref.type === 'tag'){
318 323 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
319 324 } else if (commit_ref.type === 'book'){
320 325 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
321 326 }
322 327 return tmpl.concat(escapeHtml(commit_ref.text));
323 328 };
324 329
325 330 // takes a given html element and scrolls it down offset pixels
326 331 function offsetScroll(element, offset) {
327 332 setTimeout(function() {
328 333 var location = element.offset().top;
329 334 // some browsers use body, some use html
330 335 $('html, body').animate({ scrollTop: (location - offset) });
331 336 }, 100);
332 337 }
333 338
334 339 // scroll an element `percent`% from the top of page in `time` ms
335 340 function scrollToElement(element, percent, time) {
336 341 percent = (percent === undefined ? 25 : percent);
337 342 time = (time === undefined ? 100 : time);
338 343
339 344 var $element = $(element);
340 345 if ($element.length == 0) {
341 346 throw('Cannot scroll to {0}'.format(element))
342 347 }
343 348 var elOffset = $element.offset().top;
344 349 var elHeight = $element.height();
345 350 var windowHeight = $(window).height();
346 351 var offset = elOffset;
347 352 if (elHeight < windowHeight) {
348 353 offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2));
349 354 }
350 355 setTimeout(function() {
351 356 $('html, body').animate({ scrollTop: offset});
352 357 }, time);
353 358 }
354 359
355 360 /**
356 361 * global hooks after DOM is loaded
357 362 */
358 363 $(document).ready(function() {
359 364 firefoxAnchorFix();
360 365
361 366 $('.navigation a.menulink').on('click', function(e){
362 367 var menuitem = $(this).parent('li');
363 368 if (menuitem.hasClass('open')) {
364 369 menuitem.removeClass('open');
365 370 } else {
366 371 menuitem.addClass('open');
367 372 $(document).on('click', function(event) {
368 373 if (!$(event.target).closest(menuitem).length) {
369 374 menuitem.removeClass('open');
370 375 }
371 376 });
372 377 }
373 378 });
374 379
375 380 $('body').on('click', '.cb-lineno a', function(event) {
376 381 function sortNumber(a,b) {
377 382 return a - b;
378 383 }
379 384
380 385 var lineNo = $(this).data('lineNo');
381 386 var lineName = $(this).attr('name');
382 387
383 388 if (lineNo) {
384 389 var prevLine = $('.cb-line-selected a').data('lineNo');
385 390
386 391 // on shift, we do a range selection, if we got previous line
387 392 if (event.shiftKey && prevLine !== undefined) {
388 393 var prevLine = parseInt(prevLine);
389 394 var nextLine = parseInt(lineNo);
390 395 var pos = [prevLine, nextLine].sort(sortNumber);
391 396 var anchor = '#L{0}-{1}'.format(pos[0], pos[1]);
392 397
393 398 // single click
394 399 } else {
395 400 var nextLine = parseInt(lineNo);
396 401 var pos = [nextLine, nextLine];
397 402 var anchor = '#L{0}'.format(pos[0]);
398 403
399 404 }
400 405 // highlight
401 406 var range = [];
402 407 for (var i = pos[0]; i <= pos[1]; i++) {
403 408 range.push(i);
404 409 }
405 410 // clear old selected lines
406 411 $('.cb-line-selected').removeClass('cb-line-selected');
407 412
408 413 $.each(range, function (i, lineNo) {
409 414 var line_td = $('td.cb-lineno#L' + lineNo);
410 415
411 416 if (line_td.length) {
412 417 line_td.addClass('cb-line-selected'); // line number td
413 418 line_td.prev().addClass('cb-line-selected'); // line data
414 419 line_td.next().addClass('cb-line-selected'); // line content
415 420 }
416 421 });
417 422
418 423 } else if (lineName !== undefined) { // lineName only occurs in diffs
419 424 // clear old selected lines
420 425 $('td.cb-line-selected').removeClass('cb-line-selected');
421 426 var anchor = '#{0}'.format(lineName);
422 427 var diffmode = templateContext.session_attrs.diffmode || "sideside";
423 428
424 429 if (diffmode === "unified") {
425 430 $(this).closest('tr').find('td').addClass('cb-line-selected');
426 431 } else {
427 432 var activeTd = $(this).closest('td');
428 433 activeTd.addClass('cb-line-selected');
429 434 activeTd.next('td').addClass('cb-line-selected');
430 435 }
431 436
432 437 }
433 438
434 439 // Replace URL without jumping to it if browser supports.
435 440 // Default otherwise
436 441 if (history.pushState && anchor !== undefined) {
437 442 var new_location = location.href.rstrip('#');
438 443 if (location.hash) {
439 444 // location without hash
440 445 new_location = new_location.replace(location.hash, "");
441 446 }
442 447
443 448 // Make new anchor url
444 449 new_location = new_location + anchor;
445 450 history.pushState(true, document.title, new_location);
446 451
447 452 return false;
448 453 }
449 454
450 455 });
451 456
452 457 $('.collapse_file').on('click', function(e) {
453 458 e.stopPropagation();
454 459 if ($(e.target).is('a')) { return; }
455 460 var node = $(e.delegateTarget).first();
456 461 var icon = $($(node.children().first()).children().first());
457 462 var id = node.attr('fid');
458 463 var target = $('#'+id);
459 464 var tr = $('#tr_'+id);
460 465 var diff = $('#diff_'+id);
461 466 if(node.hasClass('expand_file')){
462 467 node.removeClass('expand_file');
463 468 icon.removeClass('expand_file_icon');
464 469 node.addClass('collapse_file');
465 470 icon.addClass('collapse_file_icon');
466 471 diff.show();
467 472 tr.show();
468 473 target.show();
469 474 } else {
470 475 node.removeClass('collapse_file');
471 476 icon.removeClass('collapse_file_icon');
472 477 node.addClass('expand_file');
473 478 icon.addClass('expand_file_icon');
474 479 diff.hide();
475 480 tr.hide();
476 481 target.hide();
477 482 }
478 483 });
479 484
480 485 $('#expand_all_files').click(function() {
481 486 $('.expand_file').each(function() {
482 487 var node = $(this);
483 488 var icon = $($(node.children().first()).children().first());
484 489 var id = $(this).attr('fid');
485 490 var target = $('#'+id);
486 491 var tr = $('#tr_'+id);
487 492 var diff = $('#diff_'+id);
488 493 node.removeClass('expand_file');
489 494 icon.removeClass('expand_file_icon');
490 495 node.addClass('collapse_file');
491 496 icon.addClass('collapse_file_icon');
492 497 diff.show();
493 498 tr.show();
494 499 target.show();
495 500 });
496 501 });
497 502
498 503 $('#collapse_all_files').click(function() {
499 504 $('.collapse_file').each(function() {
500 505 var node = $(this);
501 506 var icon = $($(node.children().first()).children().first());
502 507 var id = $(this).attr('fid');
503 508 var target = $('#'+id);
504 509 var tr = $('#tr_'+id);
505 510 var diff = $('#diff_'+id);
506 511 node.removeClass('collapse_file');
507 512 icon.removeClass('collapse_file_icon');
508 513 node.addClass('expand_file');
509 514 icon.addClass('expand_file_icon');
510 515 diff.hide();
511 516 tr.hide();
512 517 target.hide();
513 518 });
514 519 });
515 520
516 521 // Mouse over behavior for comments and line selection
517 522
518 523 // Select the line that comes from the url anchor
519 524 // At the time of development, Chrome didn't seem to support jquery's :target
520 525 // element, so I had to scroll manually
521 526
522 527 if (location.hash) {
523 528 var result = splitDelimitedHash(location.hash);
524 529 var loc = result.loc;
525 530 if (loc.length > 1) {
526 531
527 532 var highlightable_line_tds = [];
528 533
529 534 // source code line format
530 535 var page_highlights = loc.substring(
531 536 loc.indexOf('#') + 1).split('L');
532 537
533 538 if (page_highlights.length > 1) {
534 539 var highlight_ranges = page_highlights[1].split(",");
535 540 var h_lines = [];
536 541 for (var pos in highlight_ranges) {
537 542 var _range = highlight_ranges[pos].split('-');
538 543 if (_range.length === 2) {
539 544 var start = parseInt(_range[0]);
540 545 var end = parseInt(_range[1]);
541 546 if (start < end) {
542 547 for (var i = start; i <= end; i++) {
543 548 h_lines.push(i);
544 549 }
545 550 }
546 551 }
547 552 else {
548 553 h_lines.push(parseInt(highlight_ranges[pos]));
549 554 }
550 555 }
551 556 for (pos in h_lines) {
552 557 var line_td = $('td.cb-lineno#L' + h_lines[pos]);
553 558 if (line_td.length) {
554 559 highlightable_line_tds.push(line_td);
555 560 }
556 561 }
557 562 }
558 563
559 564 // now check a direct id reference (diff page)
560 565 if ($(loc).length && $(loc).hasClass('cb-lineno')) {
561 566 highlightable_line_tds.push($(loc));
562 567 }
563 568 $.each(highlightable_line_tds, function (i, $td) {
564 569 $td.addClass('cb-line-selected'); // line number td
565 570 $td.prev().addClass('cb-line-selected'); // line data
566 571 $td.next().addClass('cb-line-selected'); // line content
567 572 });
568 573
569 574 if (highlightable_line_tds.length) {
570 575 var $first_line_td = highlightable_line_tds[0];
571 576 scrollToElement($first_line_td);
572 577 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
573 578 td: $first_line_td,
574 579 remainder: result.remainder
575 580 });
576 581 }
577 582 }
578 583 }
579 584 collapsableContent();
580 585 });
581 586
582 587 var feedLifetimeOptions = function(query, initialData){
583 588 var data = {results: []};
584 589 var isQuery = typeof query.term !== 'undefined';
585 590
586 591 var section = _gettext('Lifetime');
587 592 var children = [];
588 593
589 594 //filter results
590 595 $.each(initialData.results, function(idx, value) {
591 596
592 597 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
593 598 children.push({
594 599 'id': this.id,
595 600 'text': this.text
596 601 })
597 602 }
598 603
599 604 });
600 605 data.results.push({
601 606 'text': section,
602 607 'children': children
603 608 });
604 609
605 610 if (isQuery) {
606 611
607 612 var now = moment.utc();
608 613
609 614 var parseQuery = function(entry, now){
610 615 var fmt = 'DD/MM/YYYY H:mm';
611 616 var parsed = moment.utc(entry, fmt);
612 617 var diffInMin = parsed.diff(now, 'minutes');
613 618
614 619 if (diffInMin > 0){
615 620 return {
616 621 id: diffInMin,
617 622 text: parsed.format(fmt)
618 623 }
619 624 } else {
620 625 return {
621 626 id: undefined,
622 627 text: parsed.format('DD/MM/YYYY') + ' ' + _gettext('date not in future')
623 628 }
624 629 }
625 630
626 631
627 632 };
628 633
629 634 data.results.push({
630 635 'text': _gettext('Specified expiration date'),
631 636 'children': [{
632 637 'id': parseQuery(query.term, now).id,
633 638 'text': parseQuery(query.term, now).text
634 639 }]
635 640 });
636 641 }
637 642
638 643 query.callback(data);
639 644 };
640 645
641 646
642 647 var storeUserSessionAttr = function (key, val) {
643 648
644 649 var postData = {
645 650 'key': key,
646 651 'val': val,
647 652 'csrf_token': CSRF_TOKEN
648 653 };
649 654
650 655 var success = function(o) {
651 656 return true
652 657 };
653 658
654 659 ajaxPOST(pyroutes.url('store_user_session_value'), postData, success);
655 660 return false;
656 661 };
@@ -1,1132 +1,1141 b''
1 1 ## -*- coding: utf-8 -*-
2
3 ## base64 filter
4 <%!
5 def base64(text):
6 import base64
7 from rhodecode.lib.helpers import safe_str
8 return base64.encodestring(safe_str(text))
9 %>
10
2 11 <%inherit file="root.mako"/>
3 12
4 13 <%include file="/ejs_templates/templates.html"/>
5 14
6 15 <div class="outerwrapper">
7 16 <!-- HEADER -->
8 17 <div class="header">
9 18 <div id="header-inner" class="wrapper">
10 19 <div id="logo">
11 20 <div class="logo-wrapper">
12 21 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
13 22 </div>
14 23 % if c.rhodecode_name:
15 24 <div class="branding">
16 25 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
17 26 </div>
18 27 % endif
19 28 </div>
20 29 <!-- MENU BAR NAV -->
21 30 ${self.menu_bar_nav()}
22 31 <!-- END MENU BAR NAV -->
23 32 </div>
24 33 </div>
25 34 ${self.menu_bar_subnav()}
26 35 <!-- END HEADER -->
27 36
28 37 <!-- CONTENT -->
29 38 <div id="content" class="wrapper">
30 39
31 40 <rhodecode-toast id="notifications"></rhodecode-toast>
32 41
33 42 <div class="main">
34 43 ${next.main()}
35 44 </div>
36 45 </div>
37 46 <!-- END CONTENT -->
38 47
39 48 </div>
40 49 <!-- FOOTER -->
41 50 <div id="footer">
42 51 <div id="footer-inner" class="title wrapper">
43 52 <div>
44 53 <p class="footer-link-right">
45 54 % if c.visual.show_version:
46 55 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
47 56 % endif
48 57 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
49 58 % if c.visual.rhodecode_support_url:
50 59 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
51 60 % endif
52 61 </p>
53 62 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
54 63 <p class="server-instance" style="display:${sid}">
55 64 ## display hidden instance ID if specially defined
56 65 % if c.rhodecode_instanceid:
57 66 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
58 67 % endif
59 68 </p>
60 69 </div>
61 70 </div>
62 71 </div>
63 72
64 73 <!-- END FOOTER -->
65 74
66 75 ### MAKO DEFS ###
67 76
68 77 <%def name="menu_bar_subnav()">
69 78 </%def>
70 79
71 80 <%def name="breadcrumbs(class_='breadcrumbs')">
72 81 <div class="${class_}">
73 82 ${self.breadcrumbs_links()}
74 83 </div>
75 84 </%def>
76 85
77 86 <%def name="admin_menu(active=None)">
78 87 <%
79 88 def is_active(selected):
80 89 if selected == active:
81 90 return "active"
82 91 %>
83 92
84 93 <div id="context-bar">
85 94 <div class="wrapper">
86 95 <div class="title">
87 96 <div class="title-content">
88 97 <div class="title-main">
89 98 % if c.is_super_admin:
90 99 ${_('Super Admin Panel')}
91 100 % else:
92 101 ${_('Delegated Admin Panel')}
93 102 % endif
94 103 </div>
95 104 </div>
96 105 </div>
97 106
98 107 <ul id="context-pages" class="navigation horizontal-list">
99 108
100 109 ## super admin case
101 110 % if c.is_super_admin:
102 111 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
103 112 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
104 113 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
105 114 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
106 115 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
107 116 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
108 117 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
109 118 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
110 119 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
111 120 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
112 121
113 122 ## delegated admin
114 123 % elif c.is_delegated_admin:
115 124 <%
116 125 repositories=c.auth_user.repositories_admin or c.can_create_repo
117 126 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
118 127 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
119 128 %>
120 129
121 130 %if repositories:
122 131 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
123 132 %endif
124 133 %if repository_groups:
125 134 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
126 135 %endif
127 136 %if user_groups:
128 137 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
129 138 %endif
130 139 % endif
131 140 </ul>
132 141
133 142 </div>
134 143 <div class="clear"></div>
135 144 </div>
136 145 </%def>
137 146
138 147 <%def name="dt_info_panel(elements)">
139 148 <dl class="dl-horizontal">
140 149 %for dt, dd, title, show_items in elements:
141 150 <dt>${dt}:</dt>
142 151 <dd title="${h.tooltip(title)}">
143 152 %if callable(dd):
144 153 ## allow lazy evaluation of elements
145 154 ${dd()}
146 155 %else:
147 156 ${dd}
148 157 %endif
149 158 %if show_items:
150 159 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
151 160 %endif
152 161 </dd>
153 162
154 163 %if show_items:
155 164 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
156 165 %for item in show_items:
157 166 <dt></dt>
158 167 <dd>${item}</dd>
159 168 %endfor
160 169 </div>
161 170 %endif
162 171
163 172 %endfor
164 173 </dl>
165 174 </%def>
166 175
167 176 <%def name="tr_info_entry(element)">
168 177 <% key, val, title, show_items = element %>
169 178
170 179 <tr>
171 180 <td style="vertical-align: top">${key}</td>
172 181 <td title="${h.tooltip(title)}">
173 182 %if callable(val):
174 183 ## allow lazy evaluation of elements
175 184 ${val()}
176 185 %else:
177 186 ${val}
178 187 %endif
179 188 %if show_items:
180 189 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
181 190 % for item in show_items:
182 191 <dt></dt>
183 192 <dd>${item}</dd>
184 193 % endfor
185 194 </div>
186 195 %endif
187 196 </td>
188 197 <td style="vertical-align: top">
189 198 %if show_items:
190 199 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
191 200 %endif
192 201 </td>
193 202 </tr>
194 203
195 204 </%def>
196 205
197 206 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None)">
198 207 <%
199 208 if size > 16:
200 209 gravatar_class = ['gravatar','gravatar-large']
201 210 else:
202 211 gravatar_class = ['gravatar']
203 212
204 213 data_hovercard_url = ''
205 214 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
206 215
207 216 if tooltip:
208 217 gravatar_class += ['tooltip-hovercard']
209 218
210 219 if tooltip and user:
211 220 if user.username == h.DEFAULT_USER:
212 221 gravatar_class.pop(-1)
213 222 else:
214 223 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
215 224 gravatar_class = ' '.join(gravatar_class)
216 225
217 226 %>
218 227 <%doc>
219 228 TODO: johbo: For now we serve double size images to make it smooth
220 229 for retina. This is how it worked until now. Should be replaced
221 230 with a better solution at some point.
222 231 </%doc>
223 232
224 233 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
225 234 </%def>
226 235
227 236
228 237 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False)">
229 238 <%
230 239 email = h.email_or_none(contact)
231 240 rc_user = h.discover_user(contact)
232 241 %>
233 242
234 243 <div class="rc-user">
235 244 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
236 245 <span class="${('user user-disabled' if show_disabled else 'user')}"> ${h.link_to_user(rc_user or contact)}</span>
237 246 </div>
238 247 </%def>
239 248
240 249
241 250 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
242 251 <%
243 252 if (size > 16):
244 253 gravatar_class = 'icon-user-group-alt'
245 254 else:
246 255 gravatar_class = 'icon-user-group-alt'
247 256
248 257 if tooltip:
249 258 gravatar_class += ' tooltip-hovercard'
250 259
251 260 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
252 261 %>
253 262 <%doc>
254 263 TODO: johbo: For now we serve double size images to make it smooth
255 264 for retina. This is how it worked until now. Should be replaced
256 265 with a better solution at some point.
257 266 </%doc>
258 267
259 268 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
260 269 </%def>
261 270
262 271 <%def name="repo_page_title(repo_instance)">
263 272 <div class="title-content repo-title">
264 273
265 274 <div class="title-main">
266 275 ## SVN/HG/GIT icons
267 276 %if h.is_hg(repo_instance):
268 277 <i class="icon-hg"></i>
269 278 %endif
270 279 %if h.is_git(repo_instance):
271 280 <i class="icon-git"></i>
272 281 %endif
273 282 %if h.is_svn(repo_instance):
274 283 <i class="icon-svn"></i>
275 284 %endif
276 285
277 286 ## public/private
278 287 %if repo_instance.private:
279 288 <i class="icon-repo-private"></i>
280 289 %else:
281 290 <i class="icon-repo-public"></i>
282 291 %endif
283 292
284 293 ## repo name with group name
285 294 ${h.breadcrumb_repo_link(repo_instance)}
286 295
287 296 ## Context Actions
288 297 <div class="pull-right">
289 298 %if c.rhodecode_user.username != h.DEFAULT_USER:
290 299 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
291 300
292 301 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
293 302 % if c.repository_is_user_following:
294 303 <i class="icon-eye-off"></i>${_('Unwatch')}
295 304 % else:
296 305 <i class="icon-eye"></i>${_('Watch')}
297 306 % endif
298 307
299 308 </a>
300 309 %else:
301 310 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
302 311 %endif
303 312 </div>
304 313
305 314 </div>
306 315
307 316 ## FORKED
308 317 %if repo_instance.fork:
309 318 <p class="discreet">
310 319 <i class="icon-code-fork"></i> ${_('Fork of')}
311 320 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
312 321 </p>
313 322 %endif
314 323
315 324 ## IMPORTED FROM REMOTE
316 325 %if repo_instance.clone_uri:
317 326 <p class="discreet">
318 327 <i class="icon-code-fork"></i> ${_('Clone from')}
319 328 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
320 329 </p>
321 330 %endif
322 331
323 332 ## LOCKING STATUS
324 333 %if repo_instance.locked[0]:
325 334 <p class="locking_locked discreet">
326 335 <i class="icon-repo-lock"></i>
327 336 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
328 337 </p>
329 338 %elif repo_instance.enable_locking:
330 339 <p class="locking_unlocked discreet">
331 340 <i class="icon-repo-unlock"></i>
332 341 ${_('Repository not locked. Pull repository to lock it.')}
333 342 </p>
334 343 %endif
335 344
336 345 </div>
337 346 </%def>
338 347
339 348 <%def name="repo_menu(active=None)">
340 349 <%
341 350 def is_active(selected):
342 351 if selected == active:
343 352 return "active"
344 353 ## determine if we have "any" option available
345 354 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
346 355 has_actions = can_lock
347 356
348 357 %>
349 358 % if c.rhodecode_db_repo.archived:
350 359 <div class="alert alert-warning text-center">
351 360 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
352 361 </div>
353 362 % endif
354 363
355 364 <!--- REPO CONTEXT BAR -->
356 365 <div id="context-bar">
357 366 <div class="wrapper">
358 367
359 368 <div class="title">
360 369 ${self.repo_page_title(c.rhodecode_db_repo)}
361 370 </div>
362 371
363 372 <ul id="context-pages" class="navigation horizontal-list">
364 373 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
365 374 <li class="${is_active('commits')}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
366 375 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
367 376 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
368 377
369 378 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
370 379 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
371 380 <li class="${is_active('showpullrequest')}">
372 381 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
373 382 <div class="menulabel">
374 383 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
375 384 </div>
376 385 </a>
377 386 </li>
378 387 %endif
379 388
380 389 <li class="${is_active('artifacts')}">
381 390 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
382 391 <div class="menulabel">
383 392 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
384 393 </div>
385 394 </a>
386 395 </li>
387 396
388 397 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
389 398 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
390 399 %endif
391 400
392 401 <li class="${is_active('options')}">
393 402 % if has_actions:
394 403 <a class="menulink dropdown">
395 404 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
396 405 </a>
397 406 <ul class="submenu">
398 407 %if can_lock:
399 408 %if c.rhodecode_db_repo.locked[0]:
400 409 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
401 410 %else:
402 411 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
403 412 %endif
404 413 %endif
405 414 </ul>
406 415 % else:
407 416 <a class="menulink disabled">
408 417 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
409 418 </a>
410 419 % endif
411 420 </li>
412 421
413 422 </ul>
414 423 </div>
415 424 <div class="clear"></div>
416 425 </div>
417 426
418 427 <!--- REPO END CONTEXT BAR -->
419 428
420 429 </%def>
421 430
422 431 <%def name="repo_group_page_title(repo_group_instance)">
423 432 <div class="title-content">
424 433 <div class="title-main">
425 434 ## Repository Group icon
426 435 <i class="icon-repo-group"></i>
427 436
428 437 ## repo name with group name
429 438 ${h.breadcrumb_repo_group_link(repo_group_instance)}
430 439 </div>
431 440
432 441 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
433 442 <div class="repo-group-desc discreet">
434 443 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
435 444 </div>
436 445
437 446 </div>
438 447 </%def>
439 448
440 449
441 450 <%def name="repo_group_menu(active=None)">
442 451 <%
443 452 def is_active(selected):
444 453 if selected == active:
445 454 return "active"
446 455
447 456 gr_name = c.repo_group.group_name if c.repo_group else None
448 457 # create repositories with write permission on group is set to true
449 458 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
450 459
451 460 %>
452 461
453 462
454 463 <!--- REPO GROUP CONTEXT BAR -->
455 464 <div id="context-bar">
456 465 <div class="wrapper">
457 466 <div class="title">
458 467 ${self.repo_group_page_title(c.repo_group)}
459 468 </div>
460 469
461 470 <ul id="context-pages" class="navigation horizontal-list">
462 471 <li class="${is_active('home')}">
463 472 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
464 473 </li>
465 474 % if c.is_super_admin or group_admin:
466 475 <li class="${is_active('settings')}">
467 476 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
468 477 </li>
469 478 % endif
470 479
471 480 </ul>
472 481 </div>
473 482 <div class="clear"></div>
474 483 </div>
475 484
476 485 <!--- REPO GROUP CONTEXT BAR -->
477 486
478 487 </%def>
479 488
480 489
481 490 <%def name="usermenu(active=False)">
482 491 <%
483 492 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
484 493
485 494 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
486 495 # create repositories with write permission on group is set to true
487 496
488 497 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
489 498 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
490 499 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
491 500 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
492 501
493 502 can_create_repos = c.is_super_admin or c.can_create_repo
494 503 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
495 504
496 505 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
497 506 can_create_repo_groups_in_group = c.is_super_admin or group_admin
498 507 %>
499 508
500 509 % if not_anonymous:
501 510 <%
502 511 default_target_group = dict()
503 512 if c.rhodecode_user.personal_repo_group:
504 513 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
505 514 %>
506 515
507 516 ## create action
508 517 <li>
509 518 <a href="#create-actions" onclick="return false;" class="menulink childs">
510 519 <i class="icon-plus-circled"></i>
511 520 </a>
512 521
513 522 <div class="action-menu submenu">
514 523
515 524 <ol>
516 525 ## scope of within a repository
517 526 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
518 527 <li class="submenu-title">${_('This Repository')}</li>
519 528 <li>
520 529 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
521 530 </li>
522 531 % if can_fork:
523 532 <li>
524 533 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
525 534 </li>
526 535 % endif
527 536 % endif
528 537
529 538 ## scope of within repository groups
530 539 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
531 540 <li class="submenu-title">${_('This Repository Group')}</li>
532 541
533 542 % if can_create_repos_in_group:
534 543 <li>
535 544 <a href="${h.route_path('repo_new',_query=default_target_group)}">${_('New Repository')}</a>
536 545 </li>
537 546 % endif
538 547
539 548 % if can_create_repo_groups_in_group:
540 549 <li>
541 550 <a href="${h.route_path('repo_group_new',_query=default_target_group)}">${_(u'New Repository Group')}</a>
542 551 </li>
543 552 % endif
544 553 % endif
545 554
546 555 ## personal group
547 556 % if c.rhodecode_user.personal_repo_group:
548 557 <li class="submenu-title">Personal Group</li>
549 558
550 559 <li>
551 560 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
552 561 </li>
553 562
554 563 <li>
555 564 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
556 565 </li>
557 566 % endif
558 567
559 568 ## Global actions
560 569 <li class="submenu-title">RhodeCode</li>
561 570 % if can_create_repos:
562 571 <li>
563 572 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
564 573 </li>
565 574 % endif
566 575
567 576 % if can_create_repo_groups:
568 577 <li>
569 578 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
570 579 </li>
571 580 % endif
572 581
573 582 <li>
574 583 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
575 584 </li>
576 585
577 586 </ol>
578 587
579 588 </div>
580 589 </li>
581 590
582 591 ## notifications
583 592 <li>
584 593 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
585 594 ${c.unread_notifications}
586 595 </a>
587 596 </li>
588 597 % endif
589 598
590 599 ## USER MENU
591 600 <li id="quick_login_li" class="${'active' if active else ''}">
592 601 % if c.rhodecode_user.username == h.DEFAULT_USER:
593 602 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
594 603 ${gravatar(c.rhodecode_user.email, 20)}
595 604 <span class="user">
596 605 <span>${_('Sign in')}</span>
597 606 </span>
598 607 </a>
599 608 % else:
600 609 ## logged in user
601 610 <a id="quick_login_link" class="menulink childs">
602 611 ${gravatar(c.rhodecode_user.email, 20)}
603 612 <span class="user">
604 613 <span class="menu_link_user">${c.rhodecode_user.username}</span>
605 614 <div class="show_more"></div>
606 615 </span>
607 616 </a>
608 617 ## subnav with menu for logged in user
609 618 <div class="user-menu submenu">
610 619 <div id="quick_login">
611 620 %if c.rhodecode_user.username != h.DEFAULT_USER:
612 621 <div class="">
613 622 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
614 623 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
615 624 <div class="email">${c.rhodecode_user.email}</div>
616 625 </div>
617 626 <div class="">
618 627 <ol class="links">
619 628 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
620 629 % if c.rhodecode_user.personal_repo_group:
621 630 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
622 631 % endif
623 632 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
624 633
625 634 % if c.debug_style:
626 635 <li>
627 636 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
628 637 <div class="menulabel">${_('[Style]')}</div>
629 638 </a>
630 639 </li>
631 640 % endif
632 641
633 642 ## bookmark-items
634 643 <li class="bookmark-items">
635 644 ${_('Bookmarks')}
636 645 <div class="pull-right">
637 646 <a href="${h.route_path('my_account_bookmarks')}">
638 647
639 648 <i class="icon-cog"></i>
640 649 </a>
641 650 </div>
642 651 </li>
643 652 % if not c.bookmark_items:
644 653 <li>
645 654 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
646 655 </li>
647 656 % endif
648 657 % for item in c.bookmark_items:
649 658 <li>
650 659 % if item.repository:
651 660 <div>
652 661 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
653 662 <code>${item.position}</code>
654 663 % if item.repository.repo_type == 'hg':
655 664 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
656 665 % elif item.repository.repo_type == 'git':
657 666 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
658 667 % elif item.repository.repo_type == 'svn':
659 668 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
660 669 % endif
661 670 ${(item.title or h.shorter(item.repository.repo_name, 30))}
662 671 </a>
663 672 </div>
664 673 % elif item.repository_group:
665 674 <div>
666 675 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
667 676 <code>${item.position}</code>
668 677 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
669 678 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
670 679 </a>
671 680 </div>
672 681 % else:
673 682 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
674 683 <code>${item.position}</code>
675 684 ${item.title}
676 685 </a>
677 686 % endif
678 687 </li>
679 688 % endfor
680 689
681 690 <li class="logout">
682 691 ${h.secure_form(h.route_path('logout'), request=request)}
683 692 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
684 693 ${h.end_form()}
685 694 </li>
686 695 </ol>
687 696 </div>
688 697 %endif
689 698 </div>
690 699 </div>
691 700
692 701 % endif
693 702 </li>
694 703 </%def>
695 704
696 705 <%def name="menu_items(active=None)">
697 706 <%
698 707 def is_active(selected):
699 708 if selected == active:
700 709 return "active"
701 710 return ""
702 711 %>
703 712
704 713 <ul id="quick" class="main_nav navigation horizontal-list">
705 714 ## notice box for important system messages
706 715 <li style="display: none">
707 716 <a class="notice-box" href="#openNotice" onclick="return false">
708 717 <div class="menulabel-notice" >
709 718 0
710 719 </div>
711 720 </a>
712 721 </li>
713 722
714 723 ## Main filter
715 724 <li>
716 725 <div class="menulabel main_filter_box">
717 726 <div class="main_filter_input_box">
718 727 <ul class="searchItems">
719 728
720 729 % if c.template_context['search_context']['repo_id']:
721 730 <li class="searchTag searchTagFilter searchTagHidable" >
722 731 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
723 732 <span class="tag">
724 733 This repo
725 734 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
726 735 </span>
727 736 ##</a>
728 737 </li>
729 738 % elif c.template_context['search_context']['repo_group_id']:
730 739 <li class="searchTag searchTagFilter searchTagHidable">
731 740 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
732 741 <span class="tag">
733 742 This group
734 743 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
735 744 </span>
736 745 ##</a>
737 746 </li>
738 747 % endif
739 748
740 749 <li class="searchTagInput">
741 750 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
742 751 </li>
743 752 <li class="searchTag searchTagHelp">
744 753 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
745 754 </li>
746 755 </ul>
747 756 </div>
748 757 </div>
749 758
750 759 <div id="main_filter_help" style="display: none">
751 760 - Use '/' key to quickly access this field.
752 761
753 762 - Enter a name of repository, or repository group for quick search.
754 763
755 764 - Prefix query to allow special search:
756 765
757 766 user:admin, to search for usernames, always global
758 767
759 768 user_group:devops, to search for user groups, always global
760 769
761 770 commit:efced4, to search for commits, scoped to repositories or groups
762 771
763 772 file:models.py, to search for file paths, scoped to repositories or groups
764 773
765 774 % if c.template_context['search_context']['repo_id']:
766 775 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
767 776 % elif c.template_context['search_context']['repo_group_id']:
768 777 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
769 778 % else:
770 779 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
771 780 % endif
772 781 </div>
773 782 </li>
774 783
775 784 ## ROOT MENU
776 785 <li class="${is_active('home')}">
777 786 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
778 787 <div class="menulabel">${_('Home')}</div>
779 788 </a>
780 789 </li>
781 790
782 791 %if c.rhodecode_user.username != h.DEFAULT_USER:
783 792 <li class="${is_active('journal')}">
784 793 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
785 794 <div class="menulabel">${_('Journal')}</div>
786 795 </a>
787 796 </li>
788 797 %else:
789 798 <li class="${is_active('journal')}">
790 799 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
791 800 <div class="menulabel">${_('Public journal')}</div>
792 801 </a>
793 802 </li>
794 803 %endif
795 804
796 805 <li class="${is_active('gists')}">
797 806 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
798 807 <div class="menulabel">${_('Gists')}</div>
799 808 </a>
800 809 </li>
801 810
802 811 % if c.is_super_admin or c.is_delegated_admin:
803 812 <li class="${is_active('admin')}">
804 813 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
805 814 <div class="menulabel">${_('Admin')} </div>
806 815 </a>
807 816 </li>
808 817 % endif
809 818
810 819 ## render extra user menu
811 820 ${usermenu(active=(active=='my_account'))}
812 821
813 822 </ul>
814 823
815 824 <script type="text/javascript">
816 825 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
817 826
818 827 var formatRepoResult = function(result, container, query, escapeMarkup) {
819 828 return function(data, escapeMarkup) {
820 829 if (!data.repo_id){
821 830 return data.text; // optgroup text Repositories
822 831 }
823 832
824 833 var tmpl = '';
825 834 var repoType = data['repo_type'];
826 835 var repoName = data['text'];
827 836
828 837 if(data && data.type == 'repo'){
829 838 if(repoType === 'hg'){
830 839 tmpl += '<i class="icon-hg"></i> ';
831 840 }
832 841 else if(repoType === 'git'){
833 842 tmpl += '<i class="icon-git"></i> ';
834 843 }
835 844 else if(repoType === 'svn'){
836 845 tmpl += '<i class="icon-svn"></i> ';
837 846 }
838 847 if(data['private']){
839 848 tmpl += '<i class="icon-lock" ></i> ';
840 849 }
841 850 else if(visualShowPublicIcon){
842 851 tmpl += '<i class="icon-unlock-alt"></i> ';
843 852 }
844 853 }
845 854 tmpl += escapeMarkup(repoName);
846 855 return tmpl;
847 856
848 857 }(result, escapeMarkup);
849 858 };
850 859
851 860 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
852 861 return function(data, escapeMarkup) {
853 862 if (!data.repo_group_id){
854 863 return data.text; // optgroup text Repositories
855 864 }
856 865
857 866 var tmpl = '';
858 867 var repoGroupName = data['text'];
859 868
860 869 if(data){
861 870
862 871 tmpl += '<i class="icon-repo-group"></i> ';
863 872
864 873 }
865 874 tmpl += escapeMarkup(repoGroupName);
866 875 return tmpl;
867 876
868 877 }(result, escapeMarkup);
869 878 };
870 879
871 880 var escapeRegExChars = function (value) {
872 881 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
873 882 };
874 883
875 884 var getRepoIcon = function(repo_type) {
876 885 if (repo_type === 'hg') {
877 886 return '<i class="icon-hg"></i> ';
878 887 }
879 888 else if (repo_type === 'git') {
880 889 return '<i class="icon-git"></i> ';
881 890 }
882 891 else if (repo_type === 'svn') {
883 892 return '<i class="icon-svn"></i> ';
884 893 }
885 894 return ''
886 895 };
887 896
888 897 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
889 898
890 899 if (value.split(':').length === 2) {
891 900 value = value.split(':')[1]
892 901 }
893 902
894 903 var searchType = data['type'];
895 904 var searchSubType = data['subtype'];
896 905 var valueDisplay = data['value_display'];
897 906
898 907 var pattern = '(' + escapeRegExChars(value) + ')';
899 908
900 909 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
901 910
902 911 // highlight match
903 912 if (searchType != 'text') {
904 913 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
905 914 }
906 915
907 916 var icon = '';
908 917
909 918 if (searchType === 'hint') {
910 919 icon += '<i class="icon-repo-group"></i> ';
911 920 }
912 921 // full text search/hints
913 922 else if (searchType === 'search') {
914 923 icon += '<i class="icon-more"></i> ';
915 924 if (searchSubType !== undefined && searchSubType == 'repo') {
916 925 valueDisplay += '<div class="pull-right tag">repository</div>';
917 926 }
918 927 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
919 928 valueDisplay += '<div class="pull-right tag">repo group</div>';
920 929 }
921 930 }
922 931 // repository
923 932 else if (searchType === 'repo') {
924 933
925 934 var repoIcon = getRepoIcon(data['repo_type']);
926 935 icon += repoIcon;
927 936
928 937 if (data['private']) {
929 938 icon += '<i class="icon-lock" ></i> ';
930 939 }
931 940 else if (visualShowPublicIcon) {
932 941 icon += '<i class="icon-unlock-alt"></i> ';
933 942 }
934 943 }
935 944 // repository groups
936 945 else if (searchType === 'repo_group') {
937 946 icon += '<i class="icon-repo-group"></i> ';
938 947 }
939 948 // user group
940 949 else if (searchType === 'user_group') {
941 950 icon += '<i class="icon-group"></i> ';
942 951 }
943 952 // user
944 953 else if (searchType === 'user') {
945 954 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
946 955 }
947 956 // commit
948 957 else if (searchType === 'commit') {
949 958 var repo_data = data['repo_data'];
950 959 var repoIcon = getRepoIcon(repo_data['repository_type']);
951 960 if (repoIcon) {
952 961 icon += repoIcon;
953 962 } else {
954 963 icon += '<i class="icon-tag"></i>';
955 964 }
956 965 }
957 966 // file
958 967 else if (searchType === 'file') {
959 968 var repo_data = data['repo_data'];
960 969 var repoIcon = getRepoIcon(repo_data['repository_type']);
961 970 if (repoIcon) {
962 971 icon += repoIcon;
963 972 } else {
964 973 icon += '<i class="icon-tag"></i>';
965 974 }
966 975 }
967 976 // generic text
968 977 else if (searchType === 'text') {
969 978 icon = '';
970 979 }
971 980
972 981 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
973 982 return tmpl.format(icon, valueDisplay);
974 983 };
975 984
976 985 var handleSelect = function(element, suggestion) {
977 986 if (suggestion.type === "hint") {
978 987 // we skip action
979 988 $('#main_filter').focus();
980 989 }
981 990 else if (suggestion.type === "text") {
982 991 // we skip action
983 992 $('#main_filter').focus();
984 993
985 994 } else {
986 995 window.location = suggestion['url'];
987 996 }
988 997 };
989 998
990 999 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
991 1000 if (queryLowerCase.split(':').length === 2) {
992 1001 queryLowerCase = queryLowerCase.split(':')[1]
993 1002 }
994 1003 if (suggestion.type === "text") {
995 1004 // special case we don't want to "skip" display for
996 1005 return true
997 1006 }
998 1007 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
999 1008 };
1000 1009
1001 1010 var cleanContext = {
1002 1011 repo_view_type: null,
1003 1012
1004 1013 repo_id: null,
1005 1014 repo_name: "",
1006 1015
1007 1016 repo_group_id: null,
1008 1017 repo_group_name: null
1009 1018 };
1010 1019 var removeGoToFilter = function () {
1011 1020 $('.searchTagHidable').hide();
1012 1021 $('#main_filter').autocomplete(
1013 1022 'setOptions', {params:{search_context: cleanContext}});
1014 1023 };
1015 1024
1016 1025 $('#main_filter').autocomplete({
1017 1026 serviceUrl: pyroutes.url('goto_switcher_data'),
1018 1027 params: {
1019 1028 "search_context": templateContext.search_context
1020 1029 },
1021 1030 minChars:2,
1022 1031 maxHeight:400,
1023 1032 deferRequestBy: 300, //miliseconds
1024 1033 tabDisabled: true,
1025 1034 autoSelectFirst: false,
1026 1035 containerClass: 'autocomplete-qfilter-suggestions',
1027 1036 formatResult: autocompleteMainFilterFormatResult,
1028 1037 lookupFilter: autocompleteMainFilterResult,
1029 1038 onSelect: function (element, suggestion) {
1030 1039 handleSelect(element, suggestion);
1031 1040 return false;
1032 1041 },
1033 1042 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1034 1043 if (jqXHR !== 'abort') {
1035 1044 alert("Error during search.\nError code: {0}".format(textStatus));
1036 1045 window.location = '';
1037 1046 }
1038 1047 }
1039 1048 });
1040 1049
1041 1050 showMainFilterBox = function () {
1042 1051 $('#main_filter_help').toggle();
1043 1052 };
1044 1053
1045 1054 $('#main_filter').on('keydown.autocomplete', function (e) {
1046 1055
1047 1056 var BACKSPACE = 8;
1048 1057 var el = $(e.currentTarget);
1049 1058 if(e.which === BACKSPACE){
1050 1059 var inputVal = el.val();
1051 1060 if (inputVal === ""){
1052 1061 removeGoToFilter()
1053 1062 }
1054 1063 }
1055 1064 });
1056 1065
1057 1066 </script>
1058 1067 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1059 1068 </%def>
1060 1069
1061 1070 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1062 1071 <div class="modal-dialog">
1063 1072 <div class="modal-content">
1064 1073 <div class="modal-header">
1065 1074 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1066 1075 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1067 1076 </div>
1068 1077 <div class="modal-body">
1069 1078 <div class="block-left">
1070 1079 <table class="keyboard-mappings">
1071 1080 <tbody>
1072 1081 <tr>
1073 1082 <th></th>
1074 1083 <th>${_('Site-wide shortcuts')}</th>
1075 1084 </tr>
1076 1085 <%
1077 1086 elems = [
1078 1087 ('/', 'Use quick search box'),
1079 1088 ('g h', 'Goto home page'),
1080 1089 ('g g', 'Goto my private gists page'),
1081 1090 ('g G', 'Goto my public gists page'),
1082 1091 ('g 0-9', 'Goto bookmarked items from 0-9'),
1083 1092 ('n r', 'New repository page'),
1084 1093 ('n g', 'New gist page'),
1085 1094 ]
1086 1095 %>
1087 1096 %for key, desc in elems:
1088 1097 <tr>
1089 1098 <td class="keys">
1090 1099 <span class="key tag">${key}</span>
1091 1100 </td>
1092 1101 <td>${desc}</td>
1093 1102 </tr>
1094 1103 %endfor
1095 1104 </tbody>
1096 1105 </table>
1097 1106 </div>
1098 1107 <div class="block-left">
1099 1108 <table class="keyboard-mappings">
1100 1109 <tbody>
1101 1110 <tr>
1102 1111 <th></th>
1103 1112 <th>${_('Repositories')}</th>
1104 1113 </tr>
1105 1114 <%
1106 1115 elems = [
1107 1116 ('g s', 'Goto summary page'),
1108 1117 ('g c', 'Goto changelog page'),
1109 1118 ('g f', 'Goto files page'),
1110 1119 ('g F', 'Goto files page with file search activated'),
1111 1120 ('g p', 'Goto pull requests page'),
1112 1121 ('g o', 'Goto repository settings'),
1113 1122 ('g O', 'Goto repository access permissions settings'),
1114 1123 ]
1115 1124 %>
1116 1125 %for key, desc in elems:
1117 1126 <tr>
1118 1127 <td class="keys">
1119 1128 <span class="key tag">${key}</span>
1120 1129 </td>
1121 1130 <td>${desc}</td>
1122 1131 </tr>
1123 1132 %endfor
1124 1133 </tbody>
1125 1134 </table>
1126 1135 </div>
1127 1136 </div>
1128 1137 <div class="modal-footer">
1129 1138 </div>
1130 1139 </div><!-- /.modal-content -->
1131 1140 </div><!-- /.modal-dialog -->
1132 1141 </div><!-- /.modal -->
General Comments 0
You need to be logged in to leave comments. Login now