websupport.js 25 KB

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