docs/build/html/_static/websupport.js
changeset 79 e12e18e99a68
parent 78 54aeb42c0d80
child 80 137705383aaf
equal deleted inserted replaced
78:54aeb42c0d80 79:e12e18e99a68
     1 /*
       
     2  * websupport.js
       
     3  * ~~~~~~~~~~~~~
       
     4  *
       
     5  * sphinx.websupport utilities for all documentation.
       
     6  *
       
     7  * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
       
     8  * :license: BSD, see LICENSE for details.
       
     9  *
       
    10  */
       
    11 
       
    12 (function($) {
       
    13   $.fn.autogrow = function() {
       
    14     return this.each(function() {
       
    15     var textarea = this;
       
    16 
       
    17     $.fn.autogrow.resize(textarea);
       
    18 
       
    19     $(textarea)
       
    20       .focus(function() {
       
    21         textarea.interval = setInterval(function() {
       
    22           $.fn.autogrow.resize(textarea);
       
    23         }, 500);
       
    24       })
       
    25       .blur(function() {
       
    26         clearInterval(textarea.interval);
       
    27       });
       
    28     });
       
    29   };
       
    30 
       
    31   $.fn.autogrow.resize = function(textarea) {
       
    32     var lineHeight = parseInt($(textarea).css('line-height'), 10);
       
    33     var lines = textarea.value.split('\n');
       
    34     var columns = textarea.cols;
       
    35     var lineCount = 0;
       
    36     $.each(lines, function() {
       
    37       lineCount += Math.ceil(this.length / columns) || 1;
       
    38     });
       
    39     var height = lineHeight * (lineCount + 1);
       
    40     $(textarea).css('height', height);
       
    41   };
       
    42 })(jQuery);
       
    43 
       
    44 (function($) {
       
    45   var comp, by;
       
    46 
       
    47   function init() {
       
    48     initEvents();
       
    49     initComparator();
       
    50   }
       
    51 
       
    52   function initEvents() {
       
    53     $(document).on("click", 'a.comment-close', function(event) {
       
    54       event.preventDefault();
       
    55       hide($(this).attr('id').substring(2));
       
    56     });
       
    57     $(document).on("click", 'a.vote', function(event) {
       
    58       event.preventDefault();
       
    59       handleVote($(this));
       
    60     });
       
    61     $(document).on("click", 'a.reply', function(event) {
       
    62       event.preventDefault();
       
    63       openReply($(this).attr('id').substring(2));
       
    64     });
       
    65     $(document).on("click", 'a.close-reply', function(event) {
       
    66       event.preventDefault();
       
    67       closeReply($(this).attr('id').substring(2));
       
    68     });
       
    69     $(document).on("click", 'a.sort-option', function(event) {
       
    70       event.preventDefault();
       
    71       handleReSort($(this));
       
    72     });
       
    73     $(document).on("click", 'a.show-proposal', function(event) {
       
    74       event.preventDefault();
       
    75       showProposal($(this).attr('id').substring(2));
       
    76     });
       
    77     $(document).on("click", 'a.hide-proposal', function(event) {
       
    78       event.preventDefault();
       
    79       hideProposal($(this).attr('id').substring(2));
       
    80     });
       
    81     $(document).on("click", 'a.show-propose-change', function(event) {
       
    82       event.preventDefault();
       
    83       showProposeChange($(this).attr('id').substring(2));
       
    84     });
       
    85     $(document).on("click", 'a.hide-propose-change', function(event) {
       
    86       event.preventDefault();
       
    87       hideProposeChange($(this).attr('id').substring(2));
       
    88     });
       
    89     $(document).on("click", 'a.accept-comment', function(event) {
       
    90       event.preventDefault();
       
    91       acceptComment($(this).attr('id').substring(2));
       
    92     });
       
    93     $(document).on("click", 'a.delete-comment', function(event) {
       
    94       event.preventDefault();
       
    95       deleteComment($(this).attr('id').substring(2));
       
    96     });
       
    97     $(document).on("click", 'a.comment-markup', function(event) {
       
    98       event.preventDefault();
       
    99       toggleCommentMarkupBox($(this).attr('id').substring(2));
       
   100     });
       
   101   }
       
   102 
       
   103   /**
       
   104    * Set comp, which is a comparator function used for sorting and
       
   105    * inserting comments into the list.
       
   106    */
       
   107   function setComparator() {
       
   108     // If the first three letters are "asc", sort in ascending order
       
   109     // and remove the prefix.
       
   110     if (by.substring(0,3) == 'asc') {
       
   111       var i = by.substring(3);
       
   112       comp = function(a, b) { return a[i] - b[i]; };
       
   113     } else {
       
   114       // Otherwise sort in descending order.
       
   115       comp = function(a, b) { return b[by] - a[by]; };
       
   116     }
       
   117 
       
   118     // Reset link styles and format the selected sort option.
       
   119     $('a.sel').attr('href', '#').removeClass('sel');
       
   120     $('a.by' + by).removeAttr('href').addClass('sel');
       
   121   }
       
   122 
       
   123   /**
       
   124    * Create a comp function. If the user has preferences stored in
       
   125    * the sortBy cookie, use those, otherwise use the default.
       
   126    */
       
   127   function initComparator() {
       
   128     by = 'rating'; // Default to sort by rating.
       
   129     // If the sortBy cookie is set, use that instead.
       
   130     if (document.cookie.length > 0) {
       
   131       var start = document.cookie.indexOf('sortBy=');
       
   132       if (start != -1) {
       
   133         start = start + 7;
       
   134         var end = document.cookie.indexOf(";", start);
       
   135         if (end == -1) {
       
   136           end = document.cookie.length;
       
   137           by = unescape(document.cookie.substring(start, end));
       
   138         }
       
   139       }
       
   140     }
       
   141     setComparator();
       
   142   }
       
   143 
       
   144   /**
       
   145    * Show a comment div.
       
   146    */
       
   147   function show(id) {
       
   148     $('#ao' + id).hide();
       
   149     $('#ah' + id).show();
       
   150     var context = $.extend({id: id}, opts);
       
   151     var popup = $(renderTemplate(popupTemplate, context)).hide();
       
   152     popup.find('textarea[name="proposal"]').hide();
       
   153     popup.find('a.by' + by).addClass('sel');
       
   154     var form = popup.find('#cf' + id);
       
   155     form.submit(function(event) {
       
   156       event.preventDefault();
       
   157       addComment(form);
       
   158     });
       
   159     $('#s' + id).after(popup);
       
   160     popup.slideDown('fast', function() {
       
   161       getComments(id);
       
   162     });
       
   163   }
       
   164 
       
   165   /**
       
   166    * Hide a comment div.
       
   167    */
       
   168   function hide(id) {
       
   169     $('#ah' + id).hide();
       
   170     $('#ao' + id).show();
       
   171     var div = $('#sc' + id);
       
   172     div.slideUp('fast', function() {
       
   173       div.remove();
       
   174     });
       
   175   }
       
   176 
       
   177   /**
       
   178    * Perform an ajax request to get comments for a node
       
   179    * and insert the comments into the comments tree.
       
   180    */
       
   181   function getComments(id) {
       
   182     $.ajax({
       
   183      type: 'GET',
       
   184      url: opts.getCommentsURL,
       
   185      data: {node: id},
       
   186      success: function(data, textStatus, request) {
       
   187        var ul = $('#cl' + id);
       
   188        var speed = 100;
       
   189        $('#cf' + id)
       
   190          .find('textarea[name="proposal"]')
       
   191          .data('source', data.source);
       
   192 
       
   193        if (data.comments.length === 0) {
       
   194          ul.html('<li>No comments yet.</li>');
       
   195          ul.data('empty', true);
       
   196        } else {
       
   197          // If there are comments, sort them and put them in the list.
       
   198          var comments = sortComments(data.comments);
       
   199          speed = data.comments.length * 100;
       
   200          appendComments(comments, ul);
       
   201          ul.data('empty', false);
       
   202        }
       
   203        $('#cn' + id).slideUp(speed + 200);
       
   204        ul.slideDown(speed);
       
   205      },
       
   206      error: function(request, textStatus, error) {
       
   207        showError('Oops, there was a problem retrieving the comments.');
       
   208      },
       
   209      dataType: 'json'
       
   210     });
       
   211   }
       
   212 
       
   213   /**
       
   214    * Add a comment via ajax and insert the comment into the comment tree.
       
   215    */
       
   216   function addComment(form) {
       
   217     var node_id = form.find('input[name="node"]').val();
       
   218     var parent_id = form.find('input[name="parent"]').val();
       
   219     var text = form.find('textarea[name="comment"]').val();
       
   220     var proposal = form.find('textarea[name="proposal"]').val();
       
   221 
       
   222     if (text == '') {
       
   223       showError('Please enter a comment.');
       
   224       return;
       
   225     }
       
   226 
       
   227     // Disable the form that is being submitted.
       
   228     form.find('textarea,input').attr('disabled', 'disabled');
       
   229 
       
   230     // Send the comment to the server.
       
   231     $.ajax({
       
   232       type: "POST",
       
   233       url: opts.addCommentURL,
       
   234       dataType: 'json',
       
   235       data: {
       
   236         node: node_id,
       
   237         parent: parent_id,
       
   238         text: text,
       
   239         proposal: proposal
       
   240       },
       
   241       success: function(data, textStatus, error) {
       
   242         // Reset the form.
       
   243         if (node_id) {
       
   244           hideProposeChange(node_id);
       
   245         }
       
   246         form.find('textarea')
       
   247           .val('')
       
   248           .add(form.find('input'))
       
   249           .removeAttr('disabled');
       
   250 	var ul = $('#cl' + (node_id || parent_id));
       
   251         if (ul.data('empty')) {
       
   252           $(ul).empty();
       
   253           ul.data('empty', false);
       
   254         }
       
   255         insertComment(data.comment);
       
   256         var ao = $('#ao' + node_id);
       
   257         ao.find('img').attr({'src': opts.commentBrightImage});
       
   258         if (node_id) {
       
   259           // if this was a "root" comment, remove the commenting box
       
   260           // (the user can get it back by reopening the comment popup)
       
   261           $('#ca' + node_id).slideUp();
       
   262         }
       
   263       },
       
   264       error: function(request, textStatus, error) {
       
   265         form.find('textarea,input').removeAttr('disabled');
       
   266         showError('Oops, there was a problem adding the comment.');
       
   267       }
       
   268     });
       
   269   }
       
   270 
       
   271   /**
       
   272    * Recursively append comments to the main comment list and children
       
   273    * lists, creating the comment tree.
       
   274    */
       
   275   function appendComments(comments, ul) {
       
   276     $.each(comments, function() {
       
   277       var div = createCommentDiv(this);
       
   278       ul.append($(document.createElement('li')).html(div));
       
   279       appendComments(this.children, div.find('ul.comment-children'));
       
   280       // To avoid stagnating data, don't store the comments children in data.
       
   281       this.children = null;
       
   282       div.data('comment', this);
       
   283     });
       
   284   }
       
   285 
       
   286   /**
       
   287    * After adding a new comment, it must be inserted in the correct
       
   288    * location in the comment tree.
       
   289    */
       
   290   function insertComment(comment) {
       
   291     var div = createCommentDiv(comment);
       
   292 
       
   293     // To avoid stagnating data, don't store the comments children in data.
       
   294     comment.children = null;
       
   295     div.data('comment', comment);
       
   296 
       
   297     var ul = $('#cl' + (comment.node || comment.parent));
       
   298     var siblings = getChildren(ul);
       
   299 
       
   300     var li = $(document.createElement('li'));
       
   301     li.hide();
       
   302 
       
   303     // Determine where in the parents children list to insert this comment.
       
   304     for(i=0; i < siblings.length; i++) {
       
   305       if (comp(comment, siblings[i]) <= 0) {
       
   306         $('#cd' + siblings[i].id)
       
   307           .parent()
       
   308           .before(li.html(div));
       
   309         li.slideDown('fast');
       
   310         return;
       
   311       }
       
   312     }
       
   313 
       
   314     // If we get here, this comment rates lower than all the others,
       
   315     // or it is the only comment in the list.
       
   316     ul.append(li.html(div));
       
   317     li.slideDown('fast');
       
   318   }
       
   319 
       
   320   function acceptComment(id) {
       
   321     $.ajax({
       
   322       type: 'POST',
       
   323       url: opts.acceptCommentURL,
       
   324       data: {id: id},
       
   325       success: function(data, textStatus, request) {
       
   326         $('#cm' + id).fadeOut('fast');
       
   327         $('#cd' + id).removeClass('moderate');
       
   328       },
       
   329       error: function(request, textStatus, error) {
       
   330         showError('Oops, there was a problem accepting the comment.');
       
   331       }
       
   332     });
       
   333   }
       
   334 
       
   335   function deleteComment(id) {
       
   336     $.ajax({
       
   337       type: 'POST',
       
   338       url: opts.deleteCommentURL,
       
   339       data: {id: id},
       
   340       success: function(data, textStatus, request) {
       
   341         var div = $('#cd' + id);
       
   342         if (data == 'delete') {
       
   343           // Moderator mode: remove the comment and all children immediately
       
   344           div.slideUp('fast', function() {
       
   345             div.remove();
       
   346           });
       
   347           return;
       
   348         }
       
   349         // User mode: only mark the comment as deleted
       
   350         div
       
   351           .find('span.user-id:first')
       
   352           .text('[deleted]').end()
       
   353           .find('div.comment-text:first')
       
   354           .text('[deleted]').end()
       
   355           .find('#cm' + id + ', #dc' + id + ', #ac' + id + ', #rc' + id +
       
   356                 ', #sp' + id + ', #hp' + id + ', #cr' + id + ', #rl' + id)
       
   357           .remove();
       
   358         var comment = div.data('comment');
       
   359         comment.username = '[deleted]';
       
   360         comment.text = '[deleted]';
       
   361         div.data('comment', comment);
       
   362       },
       
   363       error: function(request, textStatus, error) {
       
   364         showError('Oops, there was a problem deleting the comment.');
       
   365       }
       
   366     });
       
   367   }
       
   368 
       
   369   function showProposal(id) {
       
   370     $('#sp' + id).hide();
       
   371     $('#hp' + id).show();
       
   372     $('#pr' + id).slideDown('fast');
       
   373   }
       
   374 
       
   375   function hideProposal(id) {
       
   376     $('#hp' + id).hide();
       
   377     $('#sp' + id).show();
       
   378     $('#pr' + id).slideUp('fast');
       
   379   }
       
   380 
       
   381   function showProposeChange(id) {
       
   382     $('#pc' + id).hide();
       
   383     $('#hc' + id).show();
       
   384     var textarea = $('#pt' + id);
       
   385     textarea.val(textarea.data('source'));
       
   386     $.fn.autogrow.resize(textarea[0]);
       
   387     textarea.slideDown('fast');
       
   388   }
       
   389 
       
   390   function hideProposeChange(id) {
       
   391     $('#hc' + id).hide();
       
   392     $('#pc' + id).show();
       
   393     var textarea = $('#pt' + id);
       
   394     textarea.val('').removeAttr('disabled');
       
   395     textarea.slideUp('fast');
       
   396   }
       
   397 
       
   398   function toggleCommentMarkupBox(id) {
       
   399     $('#mb' + id).toggle();
       
   400   }
       
   401 
       
   402   /** Handle when the user clicks on a sort by link. */
       
   403   function handleReSort(link) {
       
   404     var classes = link.attr('class').split(/\s+/);
       
   405     for (var i=0; i<classes.length; i++) {
       
   406       if (classes[i] != 'sort-option') {
       
   407 	by = classes[i].substring(2);
       
   408       }
       
   409     }
       
   410     setComparator();
       
   411     // Save/update the sortBy cookie.
       
   412     var expiration = new Date();
       
   413     expiration.setDate(expiration.getDate() + 365);
       
   414     document.cookie= 'sortBy=' + escape(by) +
       
   415                      ';expires=' + expiration.toUTCString();
       
   416     $('ul.comment-ul').each(function(index, ul) {
       
   417       var comments = getChildren($(ul), true);
       
   418       comments = sortComments(comments);
       
   419       appendComments(comments, $(ul).empty());
       
   420     });
       
   421   }
       
   422 
       
   423   /**
       
   424    * Function to process a vote when a user clicks an arrow.
       
   425    */
       
   426   function handleVote(link) {
       
   427     if (!opts.voting) {
       
   428       showError("You'll need to login to vote.");
       
   429       return;
       
   430     }
       
   431 
       
   432     var id = link.attr('id');
       
   433     if (!id) {
       
   434       // Didn't click on one of the voting arrows.
       
   435       return;
       
   436     }
       
   437     // If it is an unvote, the new vote value is 0,
       
   438     // Otherwise it's 1 for an upvote, or -1 for a downvote.
       
   439     var value = 0;
       
   440     if (id.charAt(1) != 'u') {
       
   441       value = id.charAt(0) == 'u' ? 1 : -1;
       
   442     }
       
   443     // The data to be sent to the server.
       
   444     var d = {
       
   445       comment_id: id.substring(2),
       
   446       value: value
       
   447     };
       
   448 
       
   449     // Swap the vote and unvote links.
       
   450     link.hide();
       
   451     $('#' + id.charAt(0) + (id.charAt(1) == 'u' ? 'v' : 'u') + d.comment_id)
       
   452       .show();
       
   453 
       
   454     // The div the comment is displayed in.
       
   455     var div = $('div#cd' + d.comment_id);
       
   456     var data = div.data('comment');
       
   457 
       
   458     // If this is not an unvote, and the other vote arrow has
       
   459     // already been pressed, unpress it.
       
   460     if ((d.value !== 0) && (data.vote === d.value * -1)) {
       
   461       $('#' + (d.value == 1 ? 'd' : 'u') + 'u' + d.comment_id).hide();
       
   462       $('#' + (d.value == 1 ? 'd' : 'u') + 'v' + d.comment_id).show();
       
   463     }
       
   464 
       
   465     // Update the comments rating in the local data.
       
   466     data.rating += (data.vote === 0) ? d.value : (d.value - data.vote);
       
   467     data.vote = d.value;
       
   468     div.data('comment', data);
       
   469 
       
   470     // Change the rating text.
       
   471     div.find('.rating:first')
       
   472       .text(data.rating + ' point' + (data.rating == 1 ? '' : 's'));
       
   473 
       
   474     // Send the vote information to the server.
       
   475     $.ajax({
       
   476       type: "POST",
       
   477       url: opts.processVoteURL,
       
   478       data: d,
       
   479       error: function(request, textStatus, error) {
       
   480         showError('Oops, there was a problem casting that vote.');
       
   481       }
       
   482     });
       
   483   }
       
   484 
       
   485   /**
       
   486    * Open a reply form used to reply to an existing comment.
       
   487    */
       
   488   function openReply(id) {
       
   489     // Swap out the reply link for the hide link
       
   490     $('#rl' + id).hide();
       
   491     $('#cr' + id).show();
       
   492 
       
   493     // Add the reply li to the children ul.
       
   494     var div = $(renderTemplate(replyTemplate, {id: id})).hide();
       
   495     $('#cl' + id)
       
   496       .prepend(div)
       
   497       // Setup the submit handler for the reply form.
       
   498       .find('#rf' + id)
       
   499       .submit(function(event) {
       
   500         event.preventDefault();
       
   501         addComment($('#rf' + id));
       
   502         closeReply(id);
       
   503       })
       
   504       .find('input[type=button]')
       
   505       .click(function() {
       
   506         closeReply(id);
       
   507       });
       
   508     div.slideDown('fast', function() {
       
   509       $('#rf' + id).find('textarea').focus();
       
   510     });
       
   511   }
       
   512 
       
   513   /**
       
   514    * Close the reply form opened with openReply.
       
   515    */
       
   516   function closeReply(id) {
       
   517     // Remove the reply div from the DOM.
       
   518     $('#rd' + id).slideUp('fast', function() {
       
   519       $(this).remove();
       
   520     });
       
   521 
       
   522     // Swap out the hide link for the reply link
       
   523     $('#cr' + id).hide();
       
   524     $('#rl' + id).show();
       
   525   }
       
   526 
       
   527   /**
       
   528    * Recursively sort a tree of comments using the comp comparator.
       
   529    */
       
   530   function sortComments(comments) {
       
   531     comments.sort(comp);
       
   532     $.each(comments, function() {
       
   533       this.children = sortComments(this.children);
       
   534     });
       
   535     return comments;
       
   536   }
       
   537 
       
   538   /**
       
   539    * Get the children comments from a ul. If recursive is true,
       
   540    * recursively include childrens' children.
       
   541    */
       
   542   function getChildren(ul, recursive) {
       
   543     var children = [];
       
   544     ul.children().children("[id^='cd']")
       
   545       .each(function() {
       
   546         var comment = $(this).data('comment');
       
   547         if (recursive)
       
   548           comment.children = getChildren($(this).find('#cl' + comment.id), true);
       
   549         children.push(comment);
       
   550       });
       
   551     return children;
       
   552   }
       
   553 
       
   554   /** Create a div to display a comment in. */
       
   555   function createCommentDiv(comment) {
       
   556     if (!comment.displayed && !opts.moderator) {
       
   557       return $('<div class="moderate">Thank you!  Your comment will show up '
       
   558                + 'once it is has been approved by a moderator.</div>');
       
   559     }
       
   560     // Prettify the comment rating.
       
   561     comment.pretty_rating = comment.rating + ' point' +
       
   562       (comment.rating == 1 ? '' : 's');
       
   563     // Make a class (for displaying not yet moderated comments differently)
       
   564     comment.css_class = comment.displayed ? '' : ' moderate';
       
   565     // Create a div for this comment.
       
   566     var context = $.extend({}, opts, comment);
       
   567     var div = $(renderTemplate(commentTemplate, context));
       
   568 
       
   569     // If the user has voted on this comment, highlight the correct arrow.
       
   570     if (comment.vote) {
       
   571       var direction = (comment.vote == 1) ? 'u' : 'd';
       
   572       div.find('#' + direction + 'v' + comment.id).hide();
       
   573       div.find('#' + direction + 'u' + comment.id).show();
       
   574     }
       
   575 
       
   576     if (opts.moderator || comment.text != '[deleted]') {
       
   577       div.find('a.reply').show();
       
   578       if (comment.proposal_diff)
       
   579         div.find('#sp' + comment.id).show();
       
   580       if (opts.moderator && !comment.displayed)
       
   581         div.find('#cm' + comment.id).show();
       
   582       if (opts.moderator || (opts.username == comment.username))
       
   583         div.find('#dc' + comment.id).show();
       
   584     }
       
   585     return div;
       
   586   }
       
   587 
       
   588   /**
       
   589    * A simple template renderer. Placeholders such as <%id%> are replaced
       
   590    * by context['id'] with items being escaped. Placeholders such as <#id#>
       
   591    * are not escaped.
       
   592    */
       
   593   function renderTemplate(template, context) {
       
   594     var esc = $(document.createElement('div'));
       
   595 
       
   596     function handle(ph, escape) {
       
   597       var cur = context;
       
   598       $.each(ph.split('.'), function() {
       
   599         cur = cur[this];
       
   600       });
       
   601       return escape ? esc.text(cur || "").html() : cur;
       
   602     }
       
   603 
       
   604     return template.replace(/<([%#])([\w\.]*)\1>/g, function() {
       
   605       return handle(arguments[2], arguments[1] == '%' ? true : false);
       
   606     });
       
   607   }
       
   608 
       
   609   /** Flash an error message briefly. */
       
   610   function showError(message) {
       
   611     $(document.createElement('div')).attr({'class': 'popup-error'})
       
   612       .append($(document.createElement('div'))
       
   613                .attr({'class': 'error-message'}).text(message))
       
   614       .appendTo('body')
       
   615       .fadeIn("slow")
       
   616       .delay(2000)
       
   617       .fadeOut("slow");
       
   618   }
       
   619 
       
   620   /** Add a link the user uses to open the comments popup. */
       
   621   $.fn.comment = function() {
       
   622     return this.each(function() {
       
   623       var id = $(this).attr('id').substring(1);
       
   624       var count = COMMENT_METADATA[id];
       
   625       var title = count + ' comment' + (count == 1 ? '' : 's');
       
   626       var image = count > 0 ? opts.commentBrightImage : opts.commentImage;
       
   627       var addcls = count == 0 ? ' nocomment' : '';
       
   628       $(this)
       
   629         .append(
       
   630           $(document.createElement('a')).attr({
       
   631             href: '#',
       
   632             'class': 'sphinx-comment-open' + addcls,
       
   633             id: 'ao' + id
       
   634           })
       
   635             .append($(document.createElement('img')).attr({
       
   636               src: image,
       
   637               alt: 'comment',
       
   638               title: title
       
   639             }))
       
   640             .click(function(event) {
       
   641               event.preventDefault();
       
   642               show($(this).attr('id').substring(2));
       
   643             })
       
   644         )
       
   645         .append(
       
   646           $(document.createElement('a')).attr({
       
   647             href: '#',
       
   648             'class': 'sphinx-comment-close hidden',
       
   649             id: 'ah' + id
       
   650           })
       
   651             .append($(document.createElement('img')).attr({
       
   652               src: opts.closeCommentImage,
       
   653               alt: 'close',
       
   654               title: 'close'
       
   655             }))
       
   656             .click(function(event) {
       
   657               event.preventDefault();
       
   658               hide($(this).attr('id').substring(2));
       
   659             })
       
   660         );
       
   661     });
       
   662   };
       
   663 
       
   664   var opts = {
       
   665     processVoteURL: '/_process_vote',
       
   666     addCommentURL: '/_add_comment',
       
   667     getCommentsURL: '/_get_comments',
       
   668     acceptCommentURL: '/_accept_comment',
       
   669     deleteCommentURL: '/_delete_comment',
       
   670     commentImage: '/static/_static/comment.png',
       
   671     closeCommentImage: '/static/_static/comment-close.png',
       
   672     loadingImage: '/static/_static/ajax-loader.gif',
       
   673     commentBrightImage: '/static/_static/comment-bright.png',
       
   674     upArrow: '/static/_static/up.png',
       
   675     downArrow: '/static/_static/down.png',
       
   676     upArrowPressed: '/static/_static/up-pressed.png',
       
   677     downArrowPressed: '/static/_static/down-pressed.png',
       
   678     voting: false,
       
   679     moderator: false
       
   680   };
       
   681 
       
   682   if (typeof COMMENT_OPTIONS != "undefined") {
       
   683     opts = jQuery.extend(opts, COMMENT_OPTIONS);
       
   684   }
       
   685 
       
   686   var popupTemplate = '\
       
   687     <div class="sphinx-comments" id="sc<%id%>">\
       
   688       <p class="sort-options">\
       
   689         Sort by:\
       
   690         <a href="#" class="sort-option byrating">best rated</a>\
       
   691         <a href="#" class="sort-option byascage">newest</a>\
       
   692         <a href="#" class="sort-option byage">oldest</a>\
       
   693       </p>\
       
   694       <div class="comment-header">Comments</div>\
       
   695       <div class="comment-loading" id="cn<%id%>">\
       
   696         loading comments... <img src="<%loadingImage%>" alt="" /></div>\
       
   697       <ul id="cl<%id%>" class="comment-ul"></ul>\
       
   698       <div id="ca<%id%>">\
       
   699       <p class="add-a-comment">Add a comment\
       
   700         (<a href="#" class="comment-markup" id="ab<%id%>">markup</a>):</p>\
       
   701       <div class="comment-markup-box" id="mb<%id%>">\
       
   702         reStructured text markup: <i>*emph*</i>, <b>**strong**</b>, \
       
   703         <code>``code``</code>, \
       
   704         code blocks: <code>::</code> and an indented block after blank line</div>\
       
   705       <form method="post" id="cf<%id%>" class="comment-form" action="">\
       
   706         <textarea name="comment" cols="80"></textarea>\
       
   707         <p class="propose-button">\
       
   708           <a href="#" id="pc<%id%>" class="show-propose-change">\
       
   709             Propose a change &#9657;\
       
   710           </a>\
       
   711           <a href="#" id="hc<%id%>" class="hide-propose-change">\
       
   712             Propose a change &#9663;\
       
   713           </a>\
       
   714         </p>\
       
   715         <textarea name="proposal" id="pt<%id%>" cols="80"\
       
   716                   spellcheck="false"></textarea>\
       
   717         <input type="submit" value="Add comment" />\
       
   718         <input type="hidden" name="node" value="<%id%>" />\
       
   719         <input type="hidden" name="parent" value="" />\
       
   720       </form>\
       
   721       </div>\
       
   722     </div>';
       
   723 
       
   724   var commentTemplate = '\
       
   725     <div id="cd<%id%>" class="sphinx-comment<%css_class%>">\
       
   726       <div class="vote">\
       
   727         <div class="arrow">\
       
   728           <a href="#" id="uv<%id%>" class="vote" title="vote up">\
       
   729             <img src="<%upArrow%>" />\
       
   730           </a>\
       
   731           <a href="#" id="uu<%id%>" class="un vote" title="vote up">\
       
   732             <img src="<%upArrowPressed%>" />\
       
   733           </a>\
       
   734         </div>\
       
   735         <div class="arrow">\
       
   736           <a href="#" id="dv<%id%>" class="vote" title="vote down">\
       
   737             <img src="<%downArrow%>" id="da<%id%>" />\
       
   738           </a>\
       
   739           <a href="#" id="du<%id%>" class="un vote" title="vote down">\
       
   740             <img src="<%downArrowPressed%>" />\
       
   741           </a>\
       
   742         </div>\
       
   743       </div>\
       
   744       <div class="comment-content">\
       
   745         <p class="tagline comment">\
       
   746           <span class="user-id"><%username%></span>\
       
   747           <span class="rating"><%pretty_rating%></span>\
       
   748           <span class="delta"><%time.delta%></span>\
       
   749         </p>\
       
   750         <div class="comment-text comment"><#text#></div>\
       
   751         <p class="comment-opts comment">\
       
   752           <a href="#" class="reply hidden" id="rl<%id%>">reply &#9657;</a>\
       
   753           <a href="#" class="close-reply" id="cr<%id%>">reply &#9663;</a>\
       
   754           <a href="#" id="sp<%id%>" class="show-proposal">proposal &#9657;</a>\
       
   755           <a href="#" id="hp<%id%>" class="hide-proposal">proposal &#9663;</a>\
       
   756           <a href="#" id="dc<%id%>" class="delete-comment hidden">delete</a>\
       
   757           <span id="cm<%id%>" class="moderation hidden">\
       
   758             <a href="#" id="ac<%id%>" class="accept-comment">accept</a>\
       
   759           </span>\
       
   760         </p>\
       
   761         <pre class="proposal" id="pr<%id%>">\
       
   762 <#proposal_diff#>\
       
   763         </pre>\
       
   764           <ul class="comment-children" id="cl<%id%>"></ul>\
       
   765         </div>\
       
   766         <div class="clearleft"></div>\
       
   767       </div>\
       
   768     </div>';
       
   769 
       
   770   var replyTemplate = '\
       
   771     <li>\
       
   772       <div class="reply-div" id="rd<%id%>">\
       
   773         <form id="rf<%id%>">\
       
   774           <textarea name="comment" cols="80"></textarea>\
       
   775           <input type="submit" value="Add reply" />\
       
   776           <input type="button" value="Cancel" />\
       
   777           <input type="hidden" name="parent" value="<%id%>" />\
       
   778           <input type="hidden" name="node" value="" />\
       
   779         </form>\
       
   780       </div>\
       
   781     </li>';
       
   782 
       
   783   $(document).ready(function() {
       
   784     init();
       
   785   });
       
   786 })(jQuery);
       
   787 
       
   788 $(document).ready(function() {
       
   789   // add comment anchors for all paragraphs that are commentable
       
   790   $('.sphinx-has-comment').comment();
       
   791 
       
   792   // highlight search words in search results
       
   793   $("div.context").each(function() {
       
   794     var params = $.getQueryParameters();
       
   795     var terms = (params.q) ? params.q[0].split(/\s+/) : [];
       
   796     var result = $(this);
       
   797     $.each(terms, function() {
       
   798       result.highlightText(this.toLowerCase(), 'highlighted');
       
   799     });
       
   800   });
       
   801 
       
   802   // directly open comment window if requested
       
   803   var anchor = document.location.hash;
       
   804   if (anchor.substring(0, 9) == '#comment-') {
       
   805     $('#ao' + anchor.substring(9)).click();
       
   806     document.location.hash = '#s' + anchor.substring(9);
       
   807   }
       
   808 });