]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/static/main.js
auto merge of #16381 : pnkfelix/rust/fsk-rust.md-fixes, r=alexcrichton
[rust.git] / src / librustdoc / html / static / main.js
1 // Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 /*jslint browser: true, es5: true */
12 /*globals $: true, rootPath: true */
13
14 (function() {
15     "use strict";
16     var resizeTimeout, interval;
17
18     $('.js-only').removeClass('js-only');
19
20     function getQueryStringParams() {
21         var params = {};
22         window.location.search.substring(1).split("&").
23             map(function(s) {
24                 var pair = s.split("=");
25                 params[decodeURIComponent(pair[0])] =
26                     typeof pair[1] === "undefined" ?
27                             null : decodeURIComponent(pair[1]);
28             });
29         return params;
30     }
31
32     function browserSupportsHistoryApi() {
33         return window.history && typeof window.history.pushState === "function";
34     }
35
36     function resizeShortBlocks() {
37         if (resizeTimeout) {
38             clearTimeout(resizeTimeout);
39         }
40         resizeTimeout = setTimeout(function() {
41             var contentWidth = $('.content').width();
42             $('.docblock.short').width(function() {
43                 return contentWidth - 40 - $(this).prev().width();
44             }).addClass('nowrap');
45             $('.summary-column').width(function() {
46                 return contentWidth - 40 - $(this).prev().width();
47             })
48         }, 150);
49     }
50     resizeShortBlocks();
51     $(window).on('resize', resizeShortBlocks);
52
53     function highlightSourceLines() {
54         var i, from, to, match = window.location.hash.match(/^#?(\d+)(?:-(\d+))?$/);
55         if (match) {
56             from = parseInt(match[1], 10);
57             to = Math.min(50000, parseInt(match[2] || match[1], 10));
58             from = Math.min(from, to);
59             if ($('#' + from).length === 0) {
60                 return;
61             }
62             $('#' + from)[0].scrollIntoView();
63             $('.line-numbers span').removeClass('line-highlighted');
64             for (i = from; i <= to; ++i) {
65                 $('#' + i).addClass('line-highlighted');
66             }
67         }
68     }
69     highlightSourceLines();
70     $(window).on('hashchange', highlightSourceLines);
71
72     $(document).on('keyup', function(e) {
73         if (document.activeElement.tagName === 'INPUT') {
74             return;
75         }
76
77         if (e.which === 191 && $('#help').hasClass('hidden')) { // question mark
78             e.preventDefault();
79             $('#help').removeClass('hidden');
80         } else if (e.which === 27) { // esc
81             if (!$('#help').hasClass('hidden')) {
82                 e.preventDefault();
83                 $('#help').addClass('hidden');
84             } else if (!$('#search').hasClass('hidden')) {
85                 e.preventDefault();
86                 $('#search').addClass('hidden');
87                 $('#main').removeClass('hidden');
88             }
89         } else if (e.which === 83) { // S
90             e.preventDefault();
91             $('.search-input').focus();
92         }
93     }).on('click', function(e) {
94         if (!$(e.target).closest('#help').length) {
95             $('#help').addClass('hidden');
96         }
97     });
98
99     $('.version-selector').on('change', function() {
100         var i, match,
101             url = document.location.href,
102             stripped = '',
103             len = rootPath.match(/\.\.\//g).length + 1;
104
105         for (i = 0; i < len; ++i) {
106             match = url.match(/\/[^\/]*$/);
107             if (i < len - 1) {
108                 stripped = match[0] + stripped;
109             }
110             url = url.substring(0, url.length - match[0].length);
111         }
112
113         url += '/' + $('.version-selector').val() + stripped;
114
115         document.location.href = url;
116     });
117     /**
118      * A function to compute the Levenshtein distance between two strings
119      * Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported
120      * Full License can be found at http://creativecommons.org/licenses/by-sa/3.0/legalcode
121      * This code is an unmodified version of the code written by Marco de Wit 
122      * and was found at http://stackoverflow.com/a/18514751/745719
123      */
124     var levenshtein = (function() {
125         var row2 = [];
126         return function(s1, s2) {
127             if (s1 === s2) {
128                 return 0;
129             } else {
130                 var s1_len = s1.length, s2_len = s2.length;
131                 if (s1_len && s2_len) {
132                     var i1 = 0, i2 = 0, a, b, c, c2, row = row2;
133                     while (i1 < s1_len)
134                         row[i1] = ++i1;
135                     while (i2 < s2_len) {
136                         c2 = s2.charCodeAt(i2);
137                         a = i2;
138                         ++i2;
139                         b = i2;
140                         for (i1 = 0; i1 < s1_len; ++i1) {
141                             c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
142                             a = row[i1];
143                             b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
144                             row[i1] = b;
145                         }
146                     }
147                     return b;
148                 } else {
149                     return s1_len + s2_len;
150                 }
151             }
152         };
153     })();
154
155     function initSearch(rawSearchIndex) {
156         var currentResults, index, searchIndex;
157         var MAX_LEV_DISTANCE = 3;
158         var params = getQueryStringParams();
159
160         // Populate search bar with query string search term when provided,
161         // but only if the input bar is empty. This avoid the obnoxious issue
162         // where you start trying to do a search, and the index loads, and
163         // suddenly your search is gone!
164         if ($(".search-input")[0].value === "") {
165             $(".search-input")[0].value = params.search || '';
166         }
167
168         /**
169          * Executes the query and builds an index of results
170          * @param  {[Object]} query     [The user query]
171          * @param  {[type]} max         [The maximum results returned]
172          * @param  {[type]} searchWords [The list of search words to query
173          *                               against]
174          * @return {[type]}             [A search index of results]
175          */
176         function execQuery(query, max, searchWords) {
177             var valLower = query.query.toLowerCase(),
178                 val = valLower,
179                 typeFilter = itemTypeFromName(query.type),
180                 results = [],
181                 split = valLower.split("::");
182
183             //remove empty keywords
184             for (var j = 0; j < split.length; ++j) {
185                 split[j].toLowerCase();
186                 if (split[j] === "") {
187                     split.splice(j, 1);
188                 }
189             }
190
191             // quoted values mean literal search
192             var nSearchWords = searchWords.length;
193             if ((val.charAt(0) === "\"" || val.charAt(0) === "'") &&
194                 val.charAt(val.length - 1) === val.charAt(0))
195             {
196                 val = val.substr(1, val.length - 2);
197                 for (var i = 0; i < nSearchWords; ++i) {
198                     if (searchWords[i] === val) {
199                         // filter type: ... queries
200                         if (typeFilter < 0 || typeFilter === searchIndex[i].ty) {
201                             results.push({id: i, index: -1});
202                         }
203                     }
204                     if (results.length === max) {
205                         break;
206                     }
207                 }
208             } else {
209                 // gather matching search results up to a certain maximum
210                 val = val.replace(/\_/g, "");
211                 for (var i = 0; i < split.length; ++i) {
212                     for (var j = 0; j < nSearchWords; ++j) {
213                         var lev_distance;
214                         if (searchWords[j].indexOf(split[i]) > -1 ||
215                             searchWords[j].indexOf(val) > -1 ||
216                             searchWords[j].replace(/_/g, "").indexOf(val) > -1)
217                         {
218                             // filter type: ... queries
219                             if (typeFilter < 0 || typeFilter === searchIndex[j].ty) {
220                                 results.push({
221                                     id: j,
222                                     index: searchWords[j].replace(/_/g, "").indexOf(val),
223                                     lev: 0,
224                                 });
225                             }
226                         } else if (
227                             (lev_distance = levenshtein(searchWords[j], val)) <= 
228                                 MAX_LEV_DISTANCE) {
229                             if (typeFilter < 0 || typeFilter === searchIndex[j].ty) {
230                                 results.push({
231                                     id: j,
232                                     index: 0,
233                                     // we want lev results to go lower than others
234                                     lev: lev_distance,
235                                 });
236                             }
237                         }
238                         if (results.length === max) {
239                             break;
240                         }
241                     }
242                 }
243             }
244
245             var nresults = results.length;
246             for (var i = 0; i < nresults; ++i) {
247                 results[i].word = searchWords[results[i].id];
248                 results[i].item = searchIndex[results[i].id] || {};
249             }
250             // if there are no results then return to default and fail
251             if (results.length === 0) {
252                 return [];
253             }
254
255             results.sort(function(aaa, bbb) {
256                 var a, b;
257
258                 // Sort by non levenshtein results and then levenshtein results by the distance
259                 // (less changes required to match means higher rankings)
260                 a = (aaa.lev);
261                 b = (bbb.lev);
262                 if (a !== b) return a - b;
263
264                 // sort by crate (non-current crate goes later)
265                 a = (aaa.item.crate !== window.currentCrate);
266                 b = (bbb.item.crate !== window.currentCrate);
267                 if (a !== b) return a - b;
268
269                 // sort by exact match (mismatch goes later)
270                 a = (aaa.word !== valLower);
271                 b = (bbb.word !== valLower);
272                 if (a !== b) return a - b;
273
274                 // sort by item name length (longer goes later)
275                 a = aaa.word.length;
276                 b = bbb.word.length;
277                 if (a !== b) return a - b;
278
279                 // sort by item name (lexicographically larger goes later)
280                 a = aaa.word;
281                 b = bbb.word;
282                 if (a !== b) return (a > b ? +1 : -1);
283
284                 // sort by index of keyword in item name (no literal occurrence goes later)
285                 a = (aaa.index < 0);
286                 b = (bbb.index < 0);
287                 if (a !== b) return a - b;
288                 // (later literal occurrence, if any, goes later)
289                 a = aaa.index;
290                 b = bbb.index;
291                 if (a !== b) return a - b;
292
293                 // sort by description (no description goes later)
294                 a = (aaa.item.desc === '');
295                 b = (bbb.item.desc === '');
296                 if (a !== b) return a - b;
297
298                 // sort by type (later occurrence in `itemTypes` goes later)
299                 a = aaa.item.ty;
300                 b = bbb.item.ty;
301                 if (a !== b) return a - b;
302
303                 // sort by path (lexicographically larger goes later)
304                 a = aaa.item.path;
305                 b = bbb.item.path;
306                 if (a !== b) return (a > b ? +1 : -1);
307
308                 // que sera, sera
309                 return 0;
310             });
311
312             // remove duplicates, according to the data provided
313             for (var i = results.length - 1; i > 0; i -= 1) {
314                 if (results[i].word === results[i - 1].word &&
315                     results[i].item.ty === results[i - 1].item.ty &&
316                     results[i].item.path === results[i - 1].item.path)
317                 {
318                     results[i].id = -1;
319                 }
320             }
321             for (var i = 0; i < results.length; ++i) {
322                 var result = results[i],
323                     name = result.item.name.toLowerCase(),
324                     path = result.item.path.toLowerCase(),
325                     parent = result.item.parent;
326
327                 var valid = validateResult(name, path, split, parent);
328                 if (!valid) {
329                     result.id = -1;
330                 }
331             }
332             return results;
333         }
334
335         /**
336          * Validate performs the following boolean logic. For example:
337          * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
338          * exists in (name || path || parent) OR => ("file" && "open") exists in
339          * (name || path )
340          *
341          * This could be written functionally, but I wanted to minimise
342          * functions on stack.
343          *
344          * @param  {[string]} name   [The name of the result]
345          * @param  {[string]} path   [The path of the result]
346          * @param  {[string]} keys   [The keys to be used (["file", "open"])]
347          * @param  {[object]} parent [The parent of the result]
348          * @return {[boolean]}       [Whether the result is valid or not]
349          */
350         function validateResult(name, path, keys, parent) {
351             for (var i=0; i < keys.length; ++i) {
352                 // each check is for validation so we negate the conditions and invalidate
353                 if (!( 
354                     // check for an exact name match
355                     name.toLowerCase().indexOf(keys[i]) > -1 ||
356                     // then an exact path match
357                     path.toLowerCase().indexOf(keys[i]) > -1 ||
358                     // next if there is a parent, check for exact parent match
359                     (parent !== undefined && 
360                         parent.name.toLowerCase().indexOf(keys[i]) > -1) ||
361                     // lastly check to see if the name was a levenshtein match
362                     levenshtein(name.toLowerCase(), keys[i]) <= 
363                         MAX_LEV_DISTANCE)) {
364                     return false;
365                 }
366             }
367             return true;
368         }
369
370         function getQuery() {
371             var matches, type, query, raw = $('.search-input').val();
372             query = raw;
373
374             matches = query.match(/^(fn|mod|struct|enum|trait|t(ype)?d(ef)?)\s*:\s*/i);
375             if (matches) {
376                 type = matches[1].replace(/^td$/, 'typedef')
377                                  .replace(/^tdef$/, 'typedef')
378                                  .replace(/^typed$/, 'typedef');
379                 query = query.substring(matches[0].length);
380             }
381
382             return {
383                 raw: raw,
384                 query: query,
385                 type: type,
386                 id: query + type,
387             };
388         }
389
390         function initSearchNav() {
391             var hoverTimeout, $results = $('.search-results .result');
392
393             $results.on('click', function() {
394                 var dst = $(this).find('a')[0];
395                 if (window.location.pathname == dst.pathname) {
396                     $('#search').addClass('hidden');
397                     $('#main').removeClass('hidden');
398                 }
399                 document.location.href = dst.href;
400             }).on('mouseover', function() {
401                 var $el = $(this);
402                 clearTimeout(hoverTimeout);
403                 hoverTimeout = setTimeout(function() {
404                     $results.removeClass('highlighted');
405                     $el.addClass('highlighted');
406                 }, 20);
407             });
408
409             $(document).off('keydown.searchnav');
410             $(document).on('keydown.searchnav', function(e) {
411                 var $active = $results.filter('.highlighted');
412
413                 if (e.which === 38) { // up
414                     e.preventDefault();
415                     if (!$active.length || !$active.prev()) {
416                         return;
417                     }
418
419                     $active.prev().addClass('highlighted');
420                     $active.removeClass('highlighted');
421                 } else if (e.which === 40) { // down
422                     e.preventDefault();
423                     if (!$active.length) {
424                         $results.first().addClass('highlighted');
425                     } else if ($active.next().length) {
426                         $active.next().addClass('highlighted');
427                         $active.removeClass('highlighted');
428                     }
429                 } else if (e.which === 13) { // return
430                     e.preventDefault();
431                     if ($active.length) {
432                         document.location.href = $active.find('a').prop('href');
433                     }
434                 }
435             });
436         }
437
438         function escape(content) {
439             return $('<h1/>').text(content).html();
440         }
441
442         function showResults(results) {
443             var output, shown, query = getQuery();
444
445             currentResults = query.id;
446             output = '<h1>Results for ' + escape(query.query) +
447                 (query.type ? ' (type: ' + escape(query.type) + ')' : '') + '</h1>';
448             output += '<table class="search-results">';
449
450             if (results.length > 0) {
451                 shown = [];
452
453                 results.forEach(function(item) {
454                     var name, type;
455
456                     if (shown.indexOf(item) !== -1) {
457                         return;
458                     }
459
460                     shown.push(item);
461                     name = item.name;
462                     type = itemTypes[item.ty];
463
464                     output += '<tr class="' + type + ' result"><td>';
465
466                     if (type === 'mod') {
467                         output += item.path +
468                             '::<a href="' + rootPath +
469                             item.path.replace(/::/g, '/') + '/' +
470                             name + '/index.html" class="' +
471                             type + '">' + name + '</a>';
472                     } else if (type === 'static' || type === 'reexport') {
473                         output += item.path +
474                             '::<a href="' + rootPath +
475                             item.path.replace(/::/g, '/') +
476                             '/index.html" class="' + type +
477                             '">' + name + '</a>';
478                     } else if (item.parent !== undefined) {
479                         var myparent = item.parent;
480                         var anchor = '#' + type + '.' + name;
481                         output += item.path + '::' + myparent.name +
482                             '::<a href="' + rootPath +
483                             item.path.replace(/::/g, '/') +
484                             '/' + itemTypes[myparent.ty] +
485                             '.' + myparent.name +
486                             '.html' + anchor +
487                             '" class="' + type +
488                             '">' + name + '</a>';
489                     } else {
490                         output += item.path +
491                             '::<a href="' + rootPath +
492                             item.path.replace(/::/g, '/') +
493                             '/' + type +
494                             '.' + name +
495                             '.html" class="' + type +
496                             '">' + name + '</a>';
497                     }
498
499                     output += '</td><td><span class="desc">' + item.desc +
500                         '</span></td></tr>';
501                 });
502             } else {
503                 output += 'No results :( <a href="https://duckduckgo.com/?q=' +
504                     encodeURIComponent('rust ' + query.query) +
505                     '">Try on DuckDuckGo?</a>';
506             }
507
508             output += "</p>";
509             $('#main.content').addClass('hidden');
510             $('#search.content').removeClass('hidden').html(output);
511             $('#search .desc').width($('#search').width() - 40 -
512                 $('#search td:first-child').first().width());
513             initSearchNav();
514         }
515
516         function search(e) {
517             var query,
518                 filterdata = [],
519                 obj, i, len,
520                 results = [],
521                 maxResults = 200,
522                 resultIndex;
523             var params = getQueryStringParams();
524
525             query = getQuery();
526             if (e) {
527                 e.preventDefault();
528             }
529
530             if (!query.query || query.id === currentResults) {
531                 return;
532             }
533
534             // Because searching is incremental by character, only the most
535             // recent search query is added to the browser history.
536             if (browserSupportsHistoryApi()) {
537                 if (!history.state && !params.search) {
538                     history.pushState(query, "", "?search=" +
539                                                 encodeURIComponent(query.raw));
540                 } else {
541                     history.replaceState(query, "", "?search=" +
542                                                 encodeURIComponent(query.raw));
543                 }
544             }
545
546             resultIndex = execQuery(query, 20000, index);
547             len = resultIndex.length;
548             for (i = 0; i < len; ++i) {
549                 if (resultIndex[i].id > -1) {
550                     obj = searchIndex[resultIndex[i].id];
551                     filterdata.push([obj.name, obj.ty, obj.path, obj.desc]);
552                     results.push(obj);
553                 }
554                 if (results.length >= maxResults) {
555                     break;
556                 }
557             }
558
559             showResults(results);
560         }
561
562         // This mapping table should match the discriminants of
563         // `rustdoc::html::item_type::ItemType` type in Rust.
564         var itemTypes = ["mod",
565                          "struct",
566                          "type",
567                          "fn",
568                          "type",
569                          "static",
570                          "trait",
571                          "impl",
572                          "viewitem",
573                          "tymethod",
574                          "method",
575                          "structfield",
576                          "variant",
577                          "ffi",
578                          "ffs",
579                          "macro",
580                          "primitive"];
581
582         function itemTypeFromName(typename) {
583             for (var i = 0; i < itemTypes.length; ++i) {
584                 if (itemTypes[i] === typename) return i;
585             }
586             return -1;
587         }
588
589         function buildIndex(rawSearchIndex) {
590             searchIndex = [];
591             var searchWords = [];
592             for (var crate in rawSearchIndex) {
593                 if (!rawSearchIndex.hasOwnProperty(crate)) { continue }
594
595                 // an array of [(Number) item type,
596                 //              (String) name,
597                 //              (String) full path or empty string for previous path,
598                 //              (String) description,
599                 //              (optional Number) the parent path index to `paths`]
600                 var items = rawSearchIndex[crate].items;
601                 // an array of [(Number) item type,
602                 //              (String) name]
603                 var paths = rawSearchIndex[crate].paths;
604
605                 // convert `paths` into an object form
606                 var len = paths.length;
607                 for (var i = 0; i < len; ++i) {
608                     paths[i] = {ty: paths[i][0], name: paths[i][1]};
609                 }
610
611                 // convert `items` into an object form, and construct word indices.
612                 //
613                 // before any analysis is performed lets gather the search terms to
614                 // search against apart from the rest of the data.  This is a quick
615                 // operation that is cached for the life of the page state so that
616                 // all other search operations have access to this cached data for
617                 // faster analysis operations
618                 var len = items.length;
619                 var lastPath = "";
620                 for (var i = 0; i < len; ++i) {
621                     var rawRow = items[i];
622                     var row = {crate: crate, ty: rawRow[0], name: rawRow[1],
623                                path: rawRow[2] || lastPath, desc: rawRow[3],
624                                parent: paths[rawRow[4]]};
625                     searchIndex.push(row);
626                     if (typeof row.name === "string") {
627                         var word = row.name.toLowerCase();
628                         searchWords.push(word);
629                     } else {
630                         searchWords.push("");
631                     }
632                     lastPath = row.path;
633                 }
634             }
635             return searchWords;
636         }
637
638         function startSearch() {
639             var keyUpTimeout;
640             $('.do-search').on('click', search);
641             $('.search-input').on('keyup', function() {
642                 clearTimeout(keyUpTimeout);
643                 keyUpTimeout = setTimeout(search, 100);
644             });
645
646             // Push and pop states are used to add search results to the browser
647             // history.
648             if (browserSupportsHistoryApi()) {
649                 $(window).on('popstate', function(e) {
650                     var params = getQueryStringParams();
651                     // When browsing back from search results the main page
652                     // visibility must be reset.
653                     if (!params.search) {
654                         $('#main.content').removeClass('hidden');
655                         $('#search.content').addClass('hidden');
656                     }
657                     // When browsing forward to search results the previous
658                     // search will be repeated, so the currentResults are
659                     // cleared to ensure the search is successful.
660                     currentResults = null;
661                     // Synchronize search bar with query string state and
662                     // perform the search. This will empty the bar if there's
663                     // nothing there, which lets you really go back to a
664                     // previous state with nothing in the bar.
665                     $('.search-input').val(params.search);
666                     // Some browsers fire 'onpopstate' for every page load
667                     // (Chrome), while others fire the event only when actually
668                     // popping a state (Firefox), which is why search() is
669                     // called both here and at the end of the startSearch()
670                     // function.
671                     search();
672                 });
673             }
674             search();
675         }
676
677         index = buildIndex(rawSearchIndex);
678         startSearch();
679
680         // Draw a convenient sidebar of known crates if we have a listing
681         if (rootPath == '../') {
682             var sidebar = $('.sidebar');
683             var div = $('<div>').attr('class', 'block crate');
684             div.append($('<h2>').text('Crates'));
685
686             var crates = [];
687             for (var crate in rawSearchIndex) {
688                 if (!rawSearchIndex.hasOwnProperty(crate)) { continue }
689                 crates.push(crate);
690             }
691             crates.sort();
692             for (var i = 0; i < crates.length; ++i) {
693                 var klass = 'crate';
694                 if (crates[i] == window.currentCrate) {
695                     klass += ' current';
696                 }
697                 div.append($('<a>', {'href': '../' + crates[i] + '/index.html',
698                                     'class': klass}).text(crates[i]));
699             }
700             sidebar.append(div);
701         }
702     }
703
704     window.initSearch = initSearch;
705
706     window.register_implementors = function(imp) {
707         var list = $('#implementors-list');
708         var libs = Object.getOwnPropertyNames(imp);
709         for (var i = 0; i < libs.length; ++i) {
710             if (libs[i] == currentCrate) continue;
711             var structs = imp[libs[i]];
712             for (var j = 0; j < structs.length; ++j) {
713                 var code = $('<code>').append(structs[j]);
714                 $.each(code.find('a'), function(idx, a) {
715                     var href = $(a).attr('href');
716                     if (!href.startsWith('http')) {
717                         $(a).attr('href', rootPath + $(a).attr('href'));
718                     }
719                 });
720                 var li = $('<li>').append(code);
721                 list.append(li);
722             }
723         }
724     };
725     if (window.pending_implementors) {
726         window.register_implementors(window.pending_implementors);
727     }
728
729     // See documentation in html/render.rs for what this is doing.
730     var query = getQueryStringParams();
731     if (query['gotosrc']) {
732         window.location = $('#src-' + query['gotosrc']).attr('href');
733     }
734
735     $("#expand-all").on("click", function() {
736         $(".docblock").show();
737         $(".toggle-label").hide();
738         $(".toggle-wrapper").removeClass("collapsed");
739         $(".collapse-toggle").children(".inner").html("-");
740     });
741
742     $("#collapse-all").on("click", function() {
743         $(".docblock").hide();
744         $(".toggle-label").show();
745         $(".toggle-wrapper").addClass("collapsed");
746         $(".collapse-toggle").children(".inner").html("+");
747     });
748
749     $(document).on("click", ".collapse-toggle", function() {
750         var toggle = $(this);
751         var relatedDoc = toggle.parent().next();
752         if (relatedDoc.is(".docblock")) {
753             if (relatedDoc.is(":visible")) {
754                 relatedDoc.slideUp({duration:'fast', easing:'linear'});
755                 toggle.parent(".toggle-wrapper").addClass("collapsed");
756                 toggle.children(".inner").html("+");
757                 toggle.children(".toggle-label").fadeIn();
758             } else {
759                 relatedDoc.slideDown({duration:'fast', easing:'linear'});
760                 toggle.parent(".toggle-wrapper").removeClass("collapsed");
761                 toggle.children(".inner").html("-");
762                 toggle.children(".toggle-label").hide();
763             }
764         }
765     });
766
767     $(function() {
768         var toggle = "<a href='javascript:void(0)'"
769             + "class='collapse-toggle'>[<span class='inner'>-</span>]</a>";
770
771         $(".method").each(function() {
772            if ($(this).next().is(".docblock")) {
773                $(this).children().first().after(toggle);
774            }
775         });
776
777         var mainToggle = $(toggle);
778         mainToggle.append("<span class='toggle-label' style='display:none'>"
779             + "&nbsp;Expand&nbsp;description</span></a>")
780         var wrapper =  $("<div class='toggle-wrapper'>");
781         wrapper.append(mainToggle);
782         $("#main > .docblock").before(wrapper);
783     });
784
785 }());