/** Kallithea JS Files **/ 'use strict'; if (typeof console == "undefined" || typeof console.log == "undefined"){ console = { log: function() {} } } /** * INJECT .format function into String * Usage: "My name is {0} {1}".format("Johny","Bravo") * Return "My name is Johny Bravo" * Inspired by https://gist.github.com/1049426 */ String.prototype.format = function() { function format() { var str = this; var len = arguments.length+1; var safe = undefined; var arg = undefined; // For each {0} {1} {n...} replace with the argument in that position. If // the argument is an object or an array it will be stringified to JSON. for (var i=0; i < len; arg = arguments[i++]) { safe = typeof arg === 'object' ? JSON.stringify(arg) : arg; str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe); } return str; } // Save a reference of what may already exist under the property native. // Allows for doing something like: if("".format.native) { /* use native */ } format.native = String.prototype.format; // Replace the prototype property return format; }(); String.prototype.strip = function(char) { if(char === undefined){ char = '\\s'; } return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), ''); } String.prototype.lstrip = function(char) { if(char === undefined){ char = '\\s'; } return this.replace(new RegExp('^'+char+'+'),''); } String.prototype.rstrip = function(char) { if(char === undefined){ char = '\\s'; } return this.replace(new RegExp(''+char+'+$'),''); } /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill under MIT license / public domain, see https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */ if(!Array.prototype.indexOf) { Array.prototype.indexOf = function (searchElement, fromIndex) { if ( this === undefined || this === null ) { throw new TypeError( '"this" is null or not defined' ); } var length = this.length >>> 0; // Hack to convert object.length to a UInt32 fromIndex = +fromIndex || 0; if (Math.abs(fromIndex) === Infinity) { fromIndex = 0; } if (fromIndex < 0) { fromIndex += length; if (fromIndex < 0) { fromIndex = 0; } } for (;fromIndex < length; fromIndex++) { if (this[fromIndex] === searchElement) { return fromIndex; } } return -1; }; } /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Compatibility under MIT license / public domain, see https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */ if (!Array.prototype.filter) { Array.prototype.filter = function(fun /*, thisArg */) { if (this === void 0 || this === null) throw new TypeError(); var t = Object(this); var len = t.length >>> 0; if (typeof fun !== "function") throw new TypeError(); var res = []; var thisArg = arguments.length >= 2 ? arguments[1] : void 0; for (var i = 0; i < len; i++) { if (i in t) { var val = t[i]; // NOTE: Technically this should Object.defineProperty at // the next index, as push can be affected by // properties on Object.prototype and Array.prototype. // But that method's new, and collisions should be // rare, so use the more-compatible alternative. if (fun.call(thisArg, val, i, t)) res.push(val); } } return res; }; } /** * A customized version of PyRoutes.JS from https://pypi.python.org/pypi/pyroutes.js/ * which is copyright Stephane Klein and was made available under the BSD License. * * Usage pyroutes.url('mark_error_fixed',{"error_id":error_id}) // /mark_error_fixed/ */ var pyroutes = (function() { var matchlist = {}; var sprintf = (function() { function get_type(variable) { return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); } function str_repeat(input, multiplier) { for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} return output.join(''); } var str_format = function() { if (!str_format.cache.hasOwnProperty(arguments[0])) { str_format.cache[arguments[0]] = str_format.parse(arguments[0]); } return str_format.format.call(null, str_format.cache[arguments[0]], arguments); }; str_format.format = function(parse_tree, argv) { var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; for (i = 0; i < tree_length; i++) { node_type = get_type(parse_tree[i]); if (node_type === 'string') { output.push(parse_tree[i]); } else if (node_type === 'array') { match = parse_tree[i]; // convenience purposes only if (match[2]) { // keyword argument arg = argv[cursor]; for (k = 0; k < match[2].length; k++) { if (!arg.hasOwnProperty(match[2][k])) { throw(sprintf('[sprintf] property "%s" does not exist', match[2][k])); } arg = arg[match[2][k]]; } } else if (match[1]) { // positional argument (explicit) arg = argv[match[1]]; } else { // positional argument (implicit) arg = argv[cursor++]; } if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { throw(sprintf('[sprintf] expecting number but found %s', get_type(arg))); } switch (match[8]) { case 'b': arg = arg.toString(2); break; case 'c': arg = String.fromCharCode(arg); break; case 'd': arg = parseInt(arg, 10); break; case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; case 'o': arg = arg.toString(8); break; case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; case 'u': arg = Math.abs(arg); break; case 'x': arg = arg.toString(16); break; case 'X': arg = arg.toString(16).toUpperCase(); break; } arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; pad_length = match[6] - String(arg).length; pad = match[6] ? str_repeat(pad_character, pad_length) : ''; output.push(match[5] ? arg + pad : pad + arg); } } return output.join(''); }; str_format.cache = {}; str_format.parse = function(fmt) { var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; while (_fmt) { if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { parse_tree.push(match[0]); } else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { parse_tree.push('%'); } else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { if (match[2]) { arg_names |= 1; var field_list = [], replacement_field = match[2], field_match = []; if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { field_list.push(field_match[1]); while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { field_list.push(field_match[1]); } else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { field_list.push(field_match[1]); } else { throw('[sprintf] huh?'); } } } else { throw('[sprintf] huh?'); } match[2] = field_list; } else { arg_names |= 2; } if (arg_names === 3) { throw('[sprintf] mixing positional and named placeholders is not (yet) supported'); } parse_tree.push(match); } else { throw('[sprintf] huh?'); } _fmt = _fmt.substring(match[0].length); } return parse_tree; }; return str_format; })(); var vsprintf = function(fmt, argv) { argv.unshift(fmt); return sprintf.apply(null, argv); }; return { 'url': function(route_name, params) { var result = route_name; if (typeof(params) != 'object'){ params = {}; } if (matchlist.hasOwnProperty(route_name)) { var route = matchlist[route_name]; // param substitution for(var i=0; i < route[1].length; i++) { if (!params.hasOwnProperty(route[1][i])) throw new Error(route[1][i] + ' missing in "' + route_name + '" route generation'); } result = sprintf(route[0], params); var ret = []; //extra params => GET for(var param in params){ if (route[1].indexOf(param) == -1){ ret.push(encodeURIComponent(param) + "=" + encodeURIComponent(params[param])); } } var _parts = ret.join("&"); if(_parts){ result = result +'?'+ _parts } } return result; }, 'register': function(route_name, route_tmpl, req_params) { if (typeof(req_params) != 'object') { req_params = []; } var keys = []; for (var i=0; i < req_params.length; i++) { keys.push(req_params[i]); } matchlist[route_name] = [ unescape(route_tmpl), keys ] }, '_routes': function(){ return matchlist; } } })(); /* Invoke all functions in callbacks */ var _run_callbacks = function(callbacks){ if (callbacks !== undefined){ var _l = callbacks.length; for (var i=0;i<_l;i++){ var func = callbacks[i]; if(typeof(func)=='function'){ try{ func(); }catch (err){}; } } } } /** * turns objects into GET query string */ var _toQueryString = function(o) { if(typeof o !== 'object') { return false; } var _p, _qs = []; for(_p in o) { _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p])); } return _qs.join('&'); }; /** * Load HTML into DOM using Ajax * * @param $target: load html async and place it (or an error message) here * @param success: success callback function * @param args: query parameters to pass to url */ function asynchtml(url, $target, success, args){ if(args===undefined){ args=null; } $target.html(_TM['Loading ...']).css('opacity','0.3'); return $.ajax({url: url, data: args, headers: {'X-PARTIAL-XHR': '1'}, cache: false, dataType: 'html'}) .done(function(html) { $target.html(html); $target.css('opacity','1.0'); //execute the given original callback if (success !== undefined && success) { success(); } }) .fail(function(jqXHR, textStatus, errorThrown) { if (textStatus == "abort") return; $target.html('ERROR: {0}'.format(textStatus)); $target.css('opacity','1.0'); }) ; }; var ajaxGET = function(url, success, failure) { if(failure === undefined) { failure = function(jqXHR, textStatus, errorThrown) { if (textStatus != "abort") alert("Ajax GET error: " + textStatus); }; } return $.ajax({url: url, headers: {'X-PARTIAL-XHR': '1'}, cache: false}) .done(success) .fail(failure); }; var ajaxPOST = function(url, postData, success, failure) { postData['_authentication_token'] = _authentication_token; var postData = _toQueryString(postData); if(failure === undefined) { failure = function(jqXHR, textStatus, errorThrown) { if (textStatus != "abort") alert("Error posting to server: " + textStatus); }; } return $.ajax({url: url, data: postData, type: 'POST', headers: {'X-PARTIAL-XHR': '1'}, cache: false}) .done(success) .fail(failure); }; /** * activate .show_more links * the .show_more must have an id that is the the id of an element to hide prefixed with _ * the parentnode will be displayed */ var show_more_event = function(){ $('.show_more').click(function(e){ var el = e.currentTarget; $('#' + el.id.substring(1)).hide(); $(el.parentNode).show(); }); }; var _onSuccessFollow = function(target){ var $target = $(target); var $f_cnt = $('#current_followers_count'); if ($target.hasClass('follow')) { $target.removeClass('follow').addClass('following'); $target.prop('title', _TM['Stop following this repository']); if ($f_cnt.html()) { var cnt = Number($f_cnt.html())+1; $f_cnt.html(cnt); } } else { $target.removeClass('following').addClass('follow'); $target.prop('title', _TM['Start following this repository']); if ($f_cnt.html()) { var cnt = Number($f_cnt.html())-1; $f_cnt.html(cnt); } } } var toggleFollowingRepo = function(target, follows_repository_id){ var args = 'follows_repository_id=' + follows_repository_id; args += '&_authentication_token=' + _authentication_token; $.post(TOGGLE_FOLLOW_URL, args, function(data){ _onSuccessFollow(target); }); return false; }; var showRepoSize = function(target, repo_name){ var args = '_authentication_token=' + _authentication_token; if(!$("#" + target).hasClass('loaded')){ $("#" + target).html(_TM['Loading ...']); var url = pyroutes.url('repo_size', {"repo_name":repo_name}); $.post(url, args, function(data) { $("#" + target).html(data); $("#" + target).addClass('loaded'); }); } return false; }; /** * load tooltips dynamically based on data attributes, used for .lazy-cs changeset links */ var get_changeset_tooltip = function() { var $target = $(this); var tooltip = $target.data('tooltip'); if (!tooltip) { var raw_id = $target.data('raw_id'); var repo_name = $target.data('repo_name'); var url = pyroutes.url('changeset_info', {"repo_name": repo_name, "revision": raw_id}); $.ajax(url, { async: false, success: function(data) { tooltip = data["message"]; } }); $target.data('tooltip', tooltip); } return tooltip; }; /** * activate tooltips and popups */ var tooltip_activate = function(){ function placement(p, e){ if(e.getBoundingClientRect().top > 2*$(window).height()/3){ return 'top'; }else{ return 'bottom'; } } $(document).ready(function(){ $('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: placement }); $('[data-toggle="popover"]').popover({ html: true, container: 'body', placement: placement, trigger: 'hover', template: '' }); $('.lazy-cs').tooltip({ title: get_changeset_tooltip, placement: placement }); }); }; /** * Quick filter widget * * @param target: filter input target * @param nodes: list of nodes in html we want to filter. * @param display_element function that takes current node from nodes and * does hide or show based on the node */ var q_filter = (function() { var _namespace = {}; var namespace = function (target) { if (!(target in _namespace)) { _namespace[target] = {}; } return _namespace[target]; }; return function (target, $nodes, display_element) { var $nodes = $nodes; var $q_filter_field = $('#' + target); var F = namespace(target); $q_filter_field.keyup(function (e) { clearTimeout(F.filterTimeout); F.filterTimeout = setTimeout(F.updateFilter, 600); }); F.filterTimeout = null; F.updateFilter = function () { // Reset timeout F.filterTimeout = null; var obsolete = []; var req = $q_filter_field.val().toLowerCase(); var showing = 0; $nodes.each(function () { var n = this; var target_element = display_element(n); if (req && n.innerHTML.toLowerCase().indexOf(req) == -1) { $(target_element).hide(); } else { $(target_element).show(); showing += 1; } }); $('#repo_count').html(showing); /* FIXME: don't hardcode */ } } })(); /** * Comment handling */ // move comments to their right location, inside new trs function move_comments($anchorcomments) { $anchorcomments.each(function(i, anchorcomment) { var $anchorcomment = $(anchorcomment); var target_id = $anchorcomment.data('target-id'); var $comment_div = _get_add_comment_div(target_id); var f_path = $anchorcomment.data('f_path'); var line_no = $anchorcomment.data('line_no'); if ($comment_div[0]) { $comment_div.append($anchorcomment.children()); if (f_path && line_no) { _comment_div_append_add($comment_div, f_path, line_no); } else { _comment_div_append_form($comment_div, f_path, line_no); } } else { $anchorcomment.before("Comment to {0} line {1} which is outside the diff context:".format(f_path || '?', line_no || '?')); } }); linkInlineComments($('.firstlink'), $('.comment:first-child')); } // comment bubble was clicked - insert new tr and show form function show_comment_form($bubble) { var children = $bubble.closest('tr.line').children('[id]'); var line_td_id = children[children.length - 1].id; var $comment_div = _get_add_comment_div(line_td_id); var f_path = $bubble.closest('[data-f_path]').data('f_path'); var parts = line_td_id.split('_'); var line_no = parts[parts.length-1]; comment_div_state($comment_div, f_path, line_no, true); } // return comment div for target_id - add it if it doesn't exist yet function _get_add_comment_div(target_id) { var comments_box_id = 'comments-' + target_id; var $comments_box = $('#' + comments_box_id); if (!$comments_box.length) { var html = ''.format(comments_box_id); $('#' + target_id).closest('tr').after(html); $comments_box = $('#' + comments_box_id); } return $comments_box; } // Set $comment_div state - showing or not showing form and Add button. // An Add button is shown on non-empty forms when no form is shown. // The form is controlled by show_form_opt - if undefined, form is only shown for general comments. function comment_div_state($comment_div, f_path, line_no, show_form_opt) { var show_form = show_form_opt !== undefined ? show_form_opt : !f_path && !line_no; var $forms = $comment_div.children('.comment-inline-form'); var $buttonrow = $comment_div.children('.add-button-row'); var $comments = $comment_div.children('.comment'); $forms.remove(); $buttonrow.remove(); if (show_form) { _comment_div_append_form($comment_div, f_path, line_no); } else if ($comments.length) { _comment_div_append_add($comment_div, f_path, line_no); } else { $comment_div.parent('tr').remove(); } } // append an Add button to $comment_div and hook it up to show form function _comment_div_append_add($comment_div, f_path, line_no) { var addlabel = TRANSLATION_MAP['Add Another Comment']; var $add = $('
{0}
'.format(addlabel)); $comment_div.append($add); $add.children('.add-button').click(function(e) { comment_div_state($comment_div, f_path, line_no, true); }); } // append a comment form to $comment_div function _comment_div_append_form($comment_div, f_path, line_no) { var $form_div = $('#comment-inline-form-template').children() .clone() .addClass('comment-inline-form'); $comment_div.append($form_div); var $preview = $comment_div.find("div.comment-preview"); var $form = $comment_div.find("form"); var $textarea = $form.find('textarea'); $form.submit(function(e) { e.preventDefault(); var text = $textarea.val(); var review_status = $form.find('input:radio[name=changeset_status]:checked').val(); var pr_close = $form.find('input:checkbox[name=save_close]:checked').length ? 'on' : ''; var pr_delete = $form.find('input:checkbox[name=save_delete]:checked').length ? 'delete' : ''; if (!text && !review_status && !pr_close && !pr_delete) { alert("Please provide a comment"); return false; } if (pr_delete) { if (text || review_status || pr_close) { alert('Cannot delete pull request while making other changes'); return false; } if (!confirm('Confirm to delete this pull request')) { return false; } var comments = $('.comment').size(); if (comments > 0 && !confirm('Confirm again to delete this pull request with {0} comments'.format(comments))) { return false; } } if (review_status) { var $review_status = $preview.find('.automatic-comment'); var review_status_lbl = $("#comment-inline-form-template input.status_change_radio[value='" + review_status + "']").parent().text().strip(); $review_status.find('.comment-status-label').text(review_status_lbl); $review_status.show(); } $preview.find('.comment-text div').text(text); $preview.show(); $textarea.val(''); if (f_path && line_no) { $form.hide(); } var postData = { 'text': text, 'f_path': f_path, 'line': line_no, 'changeset_status': review_status, 'save_close': pr_close, 'save_delete': pr_delete }; var success = function(json_data) { if (pr_delete) { location = json_data['location']; } else { $comment_div.append(json_data['rendered_text']); comment_div_state($comment_div, f_path, line_no); linkInlineComments($('.firstlink'), $('.comment:first-child')); if ((review_status || pr_close) && !f_path && !line_no) { // Page changed a lot - reload it after closing the submitted form comment_div_state($comment_div, f_path, line_no, false); location.reload(true); } } }; var failure = function(x, s, e) { $preview.removeClass('submitting').addClass('failed'); var $status = $preview.find('.comment-submission-status'); $('', { 'title': e, text: _TM['Unable to post'] }).replaceAll($status.contents()); $('
', { 'class': 'btn-group' }).append( $('