1/* 2 * websupport.js 3 * ~~~~~~~~~~~~~ 4 * 5 * sphinx.websupport utilities for all documentation. 6 * 7 * :copyright: Copyright 2007-2017 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 ▹\ 710 </a>\ 711 <a href="#" id="hc<%id%>" class="hide-propose-change">\ 712 Propose a change ▿\ 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 ▹</a>\ 753 <a href="#" class="close-reply" id="cr<%id%>">reply ▿</a>\ 754 <a href="#" id="sp<%id%>" class="show-proposal">proposal ▹</a>\ 755 <a href="#" id="hp<%id%>" class="hide-proposal">proposal ▿</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}); 809