sphinx_django/static/comments.js
changeset 3 de4a2ed2f34b
equal deleted inserted replaced
2:f5e18f8ed036 3:de4a2ed2f34b
       
     1 /*
       
     2  * Java Script/JQuery glue for server-side Python code and the comments/fixes
       
     3  * stuff which is being served along with the documentation.
       
     4  *
       
     5  * :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
       
     6  * :license: BSD, see LICENSE for details.
       
     7  *
       
     8  * Below is a short description of the main functions used to make the tool
       
     9  * work. Internal functions are not listed here.
       
    10  *
       
    11  **  
       
    12  *** checking/downloading/printing/adding comments and fixes ****************
       
    13  **
       
    14  *
       
    15  * load_comments(id) -> check if database file with comments for paragraph 'id'
       
    16  *                      exists on the server; if yes, invoke comments_found()
       
    17  *                      function; if not, invoke no_comments() function.
       
    18  *
       
    19  * load_fixes(id) -> check if database file with fixes for paragraph 'id'
       
    20  *                   exists on the server; if yes, invoke fixes_found()
       
    21  *                   function; if not, invoke no_fixes() function.
       
    22  *
       
    23  * no_comments(id) -> insert a link ('No comments') to the post form after
       
    24  *                    the paragraph 'id'.
       
    25  *
       
    26  * no_fixes(id) -> insert a link ('No fixes') to the post form after the
       
    27  *                 'No comments' string.
       
    28  *
       
    29  * comments_found(id) -> download the comments database file for paragraph 'id'
       
    30  *                       and print it under the paragraph.
       
    31  *
       
    32  * fixes_found(id) -> download the fixes database file for paragraph 'id'
       
    33  *                    and print it under the paragraph.
       
    34  *
       
    35  * print_comments(id, data) -> print comments collected in an array 'data'
       
    36  *                             under the paragraph 'id'.
       
    37  *                             'data' is an array of objects; every object
       
    38  *                             has properties like 'name', 'score', 'comment'.
       
    39  *                             This function also prints a link at the end
       
    40  *                             of paragraph with a number of comments hidden
       
    41  *                             (i.e. "3 Comments").
       
    42  *
       
    43  * print_fixes(id, data) -> print fixes collected in an array 'data' under
       
    44  *                          the paragraph 'id'.
       
    45  *                          The description for print_comments() applies.
       
    46  *
       
    47  * add_new_comment(id) -> ajaxify a comment post form for paragraph 'id'
       
    48  *                        and reload comments (with load_comments() function).
       
    49  *                        This function also validates the form and resets
       
    50  *                        it on successful submit.
       
    51  *
       
    52  **
       
    53  *** displaying/hiding areas *************************************************
       
    54  **
       
    55  *
       
    56  * show_hide_comments(id) -> hide fixes area, show comments area and make sure
       
    57  *                           that the main comments/fixes area is shown
       
    58  *
       
    59  * show_hide_fixes(id) -> hide comments area, show fixes area and make sure
       
    60  *                        that the main comments/fixes area is shown
       
    61  *
       
    62  * show_hide_submitFixFields(id) -> show/hide additional fields in the post
       
    63  *                                  form for fixes view (when 'I would like
       
    64  *                                  to submit a patch' checkbox is 'true')
       
    65  *                                  and fill them with data if needed
       
    66  *
       
    67  *
       
    68  **
       
    69  *** rating comments/fixes ***************************************************
       
    70  **
       
    71  *
       
    72  * comments_up_down(id, comment_no, up_down, db)
       
    73  *                   -> send a GET request to the server with three parameters:
       
    74  *                       - id: paragraph id,
       
    75  *                       - comment_no: a number of a comment to score,
       
    76  *                       - up_down: two values possible: 'up' or 'down'
       
    77  *                                  depending on what kind of action should
       
    78  *                                  be taken ('up' means score+=1, and down
       
    79  *                                  means score-=1).
       
    80  *
       
    81  **
       
    82  *** sorting comments/fixes ************************************************ 
       
    83  **
       
    84  * 
       
    85  * sort_comments(id, by, db) -> sort comments for paragraph 'id' by 'by' argument
       
    86  *                          (two values for 'by' are possible at the moment:
       
    87  *                          'score' and 'date'). The sorting order changes
       
    88  *                          automatically from increasing to decreasing with
       
    89  *                          every sort.
       
    90  *
       
    91  *
       
    92  **
       
    93  *** threading ************************************************************
       
    94  **
       
    95  *
       
    96  * new_thread(id) -> make a particular comment a new thread before submitting
       
    97  *                   it
       
    98  *
       
    99  * reply_to(id, comment_no) -> make a comment a reply to comment 'comment_no'
       
   100  *
       
   101  **
       
   102  *** developer's actions ***************************************************
       
   103  **
       
   104  *
       
   105  * delete_comment(db, id, comment_no) -> delete entry from database 'db'
       
   106  *                                       located in 'id' file under
       
   107  *                                       'comment_no' index
       
   108  *
       
   109  * commit_fix(node, id, fix_no) -> commit a fix located in 'id' file under
       
   110  *                                 'fix_no' index to the repository
       
   111  *
       
   112  **
       
   113  *** general-use functions ************************************************
       
   114  **
       
   115  *
       
   116  * empty_comments(id) -> empty the comments area
       
   117  *
       
   118  * empty_fixes(id) -> empty the fixes area
       
   119  *
       
   120  * is_developer() -> returns true when developer rights should be granted
       
   121  *                   and false when the rights should not be granted.
       
   122  *                   At jQuery level it's only about displaying some
       
   123  *                   additional/developer-only-options. Real authorization
       
   124  *                   is going on on webapp (appserver.py) level.
       
   125  */
       
   126 
       
   127 comments_path = "/comments/";
       
   128 fixes_path = "/fixes/";
       
   129 _db = undefined;
       
   130 
       
   131 // *** checking/downloading/printing/adding comments ************************
       
   132 
       
   133 function load_generic(what, id, error_func, success_func) {
       
   134    // if the div was filled with the comments before, empty it before appending
       
   135    if(_db == 'comments') {
       
   136       empty_comments(id);
       
   137    } else {
       
   138       empty_fixes(id);
       
   139    }
       
   140 
       
   141    // 'what' -> 'comments_path' or 'fixes_path' variables;
       
   142    var c_path = location.protocol + "//" + location.host + what + id;
       
   143    $.ajax({ url: c_path,
       
   144             type: 'HEAD',
       
   145             error: function() { error_func(id) },
       
   146             success: function() { success_func(id)}
       
   147          });
       
   148 }
       
   149 
       
   150 function load_comments(id) {
       
   151    return load_generic(comments_path, id, no_comments, comments_found)
       
   152 }
       
   153 
       
   154 function load_fixes(id) {
       
   155    return load_generic(fixes_path, id, no_fixes, fixes_found)
       
   156 }
       
   157 
       
   158 function get_load_cf(db) {
       
   159    if(db == 'comments' || db == comments_path) {
       
   160       return load_comments;
       
   161    }
       
   162    return load_fixes;
       
   163 }
       
   164 
       
   165 //////////////////////
       
   166 
       
   167 function not_found_generic(what, id) {
       
   168    // 'what' -> 'comment' or 'fix' strings
       
   169    plural = what=='comment'?'comments':'fixes';
       
   170    $("a[name*=" + what + "_" + id + "]").replaceWith(
       
   171          '<a name="' + what + '_' + id + '" href="#' + id + '" '
       
   172           + 'onclick="show_hide_' + plural + '(\'' + id + '\')" '
       
   173           + 'id="' + id + '">No ' + plural + '</a>'
       
   174           + '<a name="' + id + '"></a>');
       
   175 }
       
   176 
       
   177 function no_comments(id) {
       
   178    not_found_generic('comment', id);
       
   179 }
       
   180 
       
   181 function no_fixes(id) {
       
   182    not_found_generic('fix', id);
       
   183 }
       
   184 
       
   185 //////////////////////////////
       
   186 
       
   187 function found_generic(what, id, print_func) {
       
   188    var c_path = location.protocol + "//" + location.host + what + id + "?id=" + id;
       
   189    $.getJSON(c_path, function(data) {
       
   190       print_func(id, data);
       
   191    });
       
   192 }
       
   193 
       
   194 function comments_found(id) {
       
   195    found_generic(comments_path, id, print_comments);
       
   196 }
       
   197 
       
   198 function fixes_found(id) {
       
   199    found_generic(fixes_path, id, print_fixes);
       
   200 }
       
   201 
       
   202 ///////////////////////////////
       
   203 
       
   204 function print_generic(what, id, data) {
       
   205    var c_flag = (what == 'comments') ? true : false;
       
   206    var singular = (what == 'comments') ? 'comment' : 'fix';
       
   207    var isdev = is_developer();
       
   208    var node = $('div[class=submitFixFields_' + id + ']').attr('value');
       
   209    
       
   210    if(what == 'comments') {
       
   211       empty_comments(id);
       
   212    } else {
       
   213       empty_fixes(id);
       
   214    }
       
   215 
       
   216    // A 'factory' function which generates '+' and '-' buttons for every comment.
       
   217    //       'what'  is a kind of database (db for comments or for fixes)
       
   218    //         'id'  is a paragraph id.
       
   219    // 'comment_no'  is a place of a comment in the list of comments for given
       
   220    //               paragraph.
       
   221    //    'up_down'  is passed to POST method. It should be 'up' or 'down'.
       
   222    //       'sign'  is what's displayed on the button, by default it's '+' or '-'.
       
   223    function rate_comment_up_down_button(what, id, comment_no, up_down, sign) {
       
   224       return ' <a href="#' + id + '" onclick="comments_up_down(\'' + id + '\', \'' + comment_no + '\', \'' + up_down + '\', \'' + what + '\')"><b>' + sign + '</b></a> '
       
   225    }
       
   226 
       
   227    // in fixes view - show proposed diff
       
   228    function proposed_fix(c_flag, paragraph) {
       
   229       if(!c_flag) {
       
   230          return   '<tr>'
       
   231                 +   '<td>'
       
   232                 +     'Fix: ' + paragraph
       
   233                 +   '</td>'
       
   234                 + '</tr>'
       
   235                 +  "<tr><td>&nbsp;</td></tr>"
       
   236       }
       
   237       return '';
       
   238    }
       
   239 
       
   240    // for every comment/fix generate a 'Reply' button
       
   241    function link_reply_to(id, comment_no) {
       
   242       return 'Comment no.: ' + data[i].comment_no
       
   243              + ' (<a href="#" onclick="reply_to(\'' + id + '\', \'' + comment_no + '\')">reply</a>).'
       
   244    }
       
   245 
       
   246    /* <developer's actions> */
       
   247    function delete_comment_button(db, id, comment_no) {
       
   248       return '<a href="#" onclick="delete_comment(\'' + db + '\', \'' + id +  '\', \'' + comment_no + '\')">delete</a>' 
       
   249    } 
       
   250 
       
   251    function commit_button(node, id, fix_no) {
       
   252       return '<a href="#" onclick="commit_fix(\'' + node + '\', \'' +  id + '\', \''
       
   253                   + fix_no + '\')">commit</a>'
       
   254    }
       
   255 
       
   256    function developers_actions(what, id, data, i) {
       
   257        return  '<tr>'
       
   258              +  '<td>'
       
   259              +    'Admin actions: '
       
   260              +    delete_comment_button(what, id, data[i].comment_no)
       
   261              +    ', '
       
   262              +    commit_button(node, id, data[i].comment_no)
       
   263              +    '.'
       
   264              +  '</td>'
       
   265              + '</tr>' 
       
   266    }
       
   267    /* </developer's actions> */
       
   268 
       
   269    // add the comments for paragraph
       
   270    for(i=0; i<data.length; i++) {
       
   271       // 'data[i]['date'] * 1000' to convert from milliseconds to seconds
       
   272       var commentDate = new Date(data[i]['date']*1000);
       
   273 
       
   274       $('div[class*=' + what + '_for_' + id + ']').append(
       
   275            "<table name='" + id + "' width='" + data[i].width + "%' align=center "
       
   276          +  "style='background-color: #f1ffc5; border: solid 1px lightblue;'>"
       
   277          +  "<tr>"
       
   278          +    "<td>"
       
   279          +     "Name: <a href='" + data[i].url + "'>" + data[i].name + "</a>. "
       
   280          +     "(" + get_date(commentDate) + ")"
       
   281          +    '</td>'
       
   282          +  '</tr>'
       
   283          +  '<tr>'
       
   284          +    '<td>'
       
   285          +     ' Score: <span class="score"><b>' + data[i].score + '</b></span>'
       
   286          +     rate_comment_up_down_button(what, id, data[i].comment_no, 'up', '+') 
       
   287          +     rate_comment_up_down_button(what, id, data[i].comment_no, 'down', '-')
       
   288          +     '. '
       
   289          +     link_reply_to(id, data[i].comment_no)
       
   290          +    '</td>'
       
   291          +  '</tr>'
       
   292          +  (isdev ? developers_actions(what, id, data, i) : '')
       
   293          +  '<tr><td>&nbsp;</td></tr>'
       
   294          +  proposed_fix(c_flag, data[i].paragraph_diff)
       
   295          +  "<tr>"
       
   296          +    "<td>"
       
   297          +      'Comment: ' + data[i].comment
       
   298          +    "</td>"
       
   299          +  "</tr>"
       
   300          + "</table><br>");
       
   301    }
       
   302 
       
   303    // add a button for showing/hiding the comments and post form
       
   304    $("a[name*=" + singular + "_" + id + "]").replaceWith(
       
   305                                    '<a name="' + singular + '_' + id
       
   306                                     + '" href="#' + id + '"' +
       
   307                                     'onclick="show_hide_' + what + '(\'' +
       
   308                                     id + '\')" '
       
   309                                     + 'id="' + id + '">'
       
   310                                     + data.length + ' ' + what + '</a>'
       
   311                                     + '<a name="' + id + '"></a>');
       
   312 
       
   313    if(what == 'comments') {
       
   314       hide_fixes(id);
       
   315       show_comments(id);
       
   316    } else {
       
   317       hide_comments(id);
       
   318       show_fixes(id);
       
   319    }
       
   320 }
       
   321 
       
   322 
       
   323 function print_comments(id, data) {
       
   324    print_generic('comments', id, data);
       
   325 }
       
   326 
       
   327 function print_fixes(id, data) {
       
   328    print_generic('fixes', id, data);
       
   329 }
       
   330 
       
   331 /////////////////////////////
       
   332 
       
   333 function add_new_comment(id) {
       
   334    function validate(formData, jqForm, options) {
       
   335       var form = jqForm[0];
       
   336       if(!form.name.value) {
       
   337          alert("Please provide your name");
       
   338          return false;
       
   339       } else if(!form.comment.value) {
       
   340          alert("Please provide your comment");
       
   341          return false;
       
   342       } else if(form.submitFix.checked && !form.licence.checked) {
       
   343          alert("You have to agree to publish your fix on our licence!");
       
   344          return false;
       
   345       }
       
   346 
       
   347       if(form.submitFix.checked) {
       
   348          _db = 'fixes';
       
   349       } else {
       
   350          _db = 'comments';
       
   351       }
       
   352    }
       
   353 
       
   354    function form_reset() {
       
   355       $('#commentForm_' + id).resetForm()
       
   356       new_thread(id);
       
   357       if(_db == 'comments') {
       
   358          load_comments(id);
       
   359       } else {
       
   360          load_fixes(id);
       
   361       }
       
   362    }
       
   363 
       
   364    // bind 'commentForm' and provide a simple callback function 
       
   365    $('#commentForm_' + id).ajaxForm({
       
   366                                        beforeSubmit: validate,
       
   367                                        success: form_reset
       
   368                                     }); 
       
   369 }
       
   370 
       
   371 //////////////////////////////
       
   372 
       
   373 function hide_main_div(id) {
       
   374    $('div.x' + id).css('display', 'none');  
       
   375 }
       
   376 
       
   377 function show_main_div(id) {
       
   378    $('div.x' + id).css('display', 'block');  
       
   379 }
       
   380 
       
   381 function hide_submitFixFields(id) {
       
   382    $('div.submitFixFields_' + id).css('display', 'none');
       
   383 }
       
   384 
       
   385 function show_submitFixFields(id) {
       
   386    node = $('div[class=submitFixFields_' + id + ']').attr('value');
       
   387    fill_paragraph_from_repo(node, id);
       
   388    $('input[name=submitFix]').attr('checked', true);
       
   389    $('div.submitFixFields_' + id).css('display', 'block'); 
       
   390    _db = 'fixes';
       
   391 
       
   392 }
       
   393 
       
   394 function hide_comments(id) {
       
   395    $('input[name=submitFix]').attr('checked', false);
       
   396    $('div.comments_for_' + id).css('display', 'none');
       
   397 }
       
   398 
       
   399 function hide_fixes(id) {
       
   400    $('div.fixes_for_' + id).css('display', 'none');
       
   401    hide_submitFixFields(id);
       
   402 }
       
   403 
       
   404 function show_comments(id) {
       
   405    $('input[name=submitFix]').attr('checked', false);
       
   406    $('div.comments_for_' + id).css('display', 'block');
       
   407    _db = 'comments';
       
   408 }
       
   409 
       
   410 function show_fixes(id) {
       
   411    $('div.fixes_for_' + id).css('display', 'block');
       
   412    show_submitFixFields(id);
       
   413 }
       
   414 
       
   415 function mainDivIsHidden(id) {
       
   416    return $('div.x' + id).is(':hidden');
       
   417 }
       
   418 
       
   419 function show_menu_hide(what, id) {
       
   420    function sort_comments_by_button(what, id, by) {
       
   421       return '<a href="#" onclick="sort_comments(\'' + id + '\', \'' + by + '\', \'' + what + '\')">' + by + '</a>'
       
   422    }
       
   423 
       
   424    sort_hide_menu = '<table style="background: #d9fff0; border: #008f57 1px solid;" width="100%"><tr><td>'
       
   425          + '<a name="hide_main_div_'
       
   426          + id
       
   427          + '" href="#" onclick="hide_main_div(\''
       
   428          + id
       
   429          +'\')">Hide</a>'
       
   430          + '</td><td>'
       
   431          +     ' Sort by: '
       
   432          +     sort_comments_by_button(what, id, 'date')
       
   433          +     ', '
       
   434          +     sort_comments_by_button(what, id, 'score')
       
   435          +     ', '
       
   436          +     sort_comments_by_button(what, id, 'thread')
       
   437          +   '.'
       
   438          + '</td></tr></table><br>'
       
   439 
       
   440    $('div[class=hide_menu_' + id + ']').replaceWith(sort_hide_menu);
       
   441 }
       
   442 
       
   443 function show_hide_comments(id) {
       
   444    show_menu_hide('comments', id);
       
   445    hide_fixes(id);
       
   446    show_comments(id);
       
   447 
       
   448    if(mainDivIsHidden(id)) {
       
   449       show_main_div(id);
       
   450    }
       
   451 }
       
   452 
       
   453 function show_hide_fixes(id) {
       
   454    show_menu_hide('fixes', id);
       
   455    hide_comments(id);
       
   456    show_fixes(id);
       
   457 
       
   458    if(mainDivIsHidden(id)) {
       
   459       show_main_div(id);
       
   460    }
       
   461 }
       
   462 
       
   463 function show_hide_submitFixFields(id) {
       
   464    // show or hide more form fields when 'i would like to submit a fix' is true
       
   465    var fixes_area_hidden =  $('div.submitFixFields_'+id).is(':hidden');
       
   466    if(fixes_area_hidden) {
       
   467       show_submitFixFields(id);
       
   468    } else {
       
   469       hide_submitFixFields(id);
       
   470    }
       
   471 }
       
   472 
       
   473 function fill_paragraph_from_repo(node, id) {
       
   474    var p_path = location.protocol + "//" + location.host
       
   475                 + "/get_paragraph?node=" + node + "&id=" + id;
       
   476    $.getJSON(p_path, function(data) {
       
   477         $('textarea[name=paragraph_' + id + ']').replaceWith(
       
   478            '<textarea name="paragraph_' + id + '" rows=10 cols=70>' + data + '</textarea>');
       
   479         $('textarea[name=paragraph_orig_' + id + ']').replaceWith(
       
   480            '<textarea name="paragraph_orig_' + id + '" style="display: none;">' + data + '</textarea>');
       
   481 
       
   482    });
       
   483    
       
   484 }
       
   485 
       
   486 // *** rating comments ******************************************************
       
   487 
       
   488 function comments_up_down(id, comment_no, up_down, db) {
       
   489    // request for a 'score' change
       
   490    load_func = get_load_cf(db);
       
   491    var rate_path = location.protocol + "//" + location.host + "/rate_comment";
       
   492    $.ajax({ url: rate_path,
       
   493             type: 'GET',
       
   494             data: "id=" + id + "&comment_no=" + comment_no + "&up_down=" + up_down + "&db=" + db,
       
   495             success: function() { load_func(id)}
       
   496          });
       
   497 }
       
   498 
       
   499 // *** developer actions ****************************************************
       
   500 
       
   501 function delete_comment(db, id, comment_no) {
       
   502    load_func = get_load_cf(db);
       
   503    var rate_path = location.protocol + "//" + location.host + "/delete_comment";
       
   504    $.ajax({ url: rate_path,
       
   505             type: 'GET',
       
   506             data: "id=" + id + "&cno=" + comment_no + "&db=" + db,
       
   507             success: function() { load_func(id)}
       
   508          });
       
   509 }
       
   510 
       
   511 function commit_fix(node, id, fix_no) {
       
   512    var c_path = location.protocol + "//" + location.host + "/commit_fix";
       
   513    $.ajax({ url: c_path,
       
   514             type: 'GET',
       
   515             data: "node=" + node + "&id=" + id + "&fix_no=" + fix_no,
       
   516             success: function() { load_func(id)}
       
   517          });
       
   518 }
       
   519 
       
   520 // *** sorting comments *****************************************************
       
   521 
       
   522 function sort_comments(id, by, db) {
       
   523    // request for a sort of comments, display json data structure ('data')
       
   524    var c_path = location.protocol + "//" + location.host
       
   525                 + "/sort_comments?id=" + id + "&by=" + by + "&db=" + db;
       
   526    $.getJSON(c_path, function(data) {
       
   527          if(db=='comments') {
       
   528             print_comments(id, data);
       
   529          } else {
       
   530             print_fixes(id, data);
       
   531          }
       
   532    });
       
   533 }
       
   534 
       
   535 // *** general-use functions ***********************************************
       
   536 
       
   537 function empty_comments(id) {
       
   538    $('div[class=comments_for_' + id + ']').replaceWith('<div class="comments_for_' + id + '" style=\"display: none;\"> </div>');
       
   539 }
       
   540 
       
   541 function empty_fixes(id) {
       
   542    $('div[class=fixes_for_' + id + ']').replaceWith('<div class="fixes_for_' + id + '" style=\"display: none;\"> </div>');
       
   543 }
       
   544 
       
   545 function is_developer() {
       
   546    var isdev_path = location.protocol + "//" + location.host + "/isdeveloper";
       
   547    http = new XMLHttpRequest();
       
   548    http.open('HEAD', isdev_path, false);
       
   549    http.send(null);
       
   550    return http.status!=404;
       
   551 }
       
   552 
       
   553 function get_date(d) {
       
   554    var month=["January", "February", "March", "April",
       
   555               "May", "June", "July", "August",
       
   556               "September", "October", "November", "December"];
       
   557    var t_date = d.getDate();      // Returns the day of the month
       
   558    var t_mon = d.getMonth();      // Returns the month as a digit
       
   559    var t_year = d.getFullYear();  // Returns 4 digit year
       
   560    var t_hour = d.getHours();     // Returns hours
       
   561    var t_min = d.getMinutes();    // Returns minutes
       
   562    var t_sec = d.getSeconds();    // Returns seocnds
       
   563    return t_hour + ':'
       
   564           + (t_min < 10 ? '0' : '') + t_min
       
   565           + ', ' + month[t_mon] + ' ' + t_date + ' ' + t_year
       
   566 }
       
   567 
       
   568 // *** threading **********************************************************
       
   569 
       
   570 function reply_to(id, comment_no) {
       
   571    $('span[name=replyto_' + id + ']').replaceWith('<span name="replyto_' + id + '">' + comment_no + '</span>');
       
   572    $('textarea[name=replyto_' + id + ']').replaceWith('<textarea name="replyto_' + id + '" style="display: none;">' + comment_no + '</textarea>');
       
   573 }
       
   574 
       
   575 function new_thread(id) {
       
   576    $('span[name=replyto_' + id + ']').replaceWith('<span name="replyto_' + id + '">new thread</span>');
       
   577    $('textarea[name=replyto_' + id + ']').replaceWith('<textarea name="replyto_' + id + '" style="display: none;"></textarea>');
       
   578 }
       
   579 
       
   580 $(document).ready(function() {
       
   581    // load the comments for each paragraph when the DOM is ready
       
   582    $("span[name*=paragraph]").each(function() {
       
   583       var id = $(this).attr('value');
       
   584       load_comments(id);
       
   585       load_fixes(id);
       
   586    });
       
   587 });