]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/static/main.js
Doc says to avoid mixing allocator instead of forbiding it
[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                     document.location.href = dst.href;
399                 }
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, href, displayPath;
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                     if (type === 'mod') {
465                         displayPath = item.path + '::';
466                         href = rootPath + item.path.replace(/::/g, '/') + '/' +
467                                name + '/index.html';
468                     } else if (type === 'static' || type === 'reexport') {
469                         displayPath = item.path + '::';
470                         href = rootPath + item.path.replace(/::/g, '/') +
471                                '/index.html';
472                     } else if (item.parent !== undefined) {
473                         var myparent = item.parent;
474                         var anchor = '#' + type + '.' + name;
475                         displayPath = item.path + '::' + myparent.name + '::';
476                         href = rootPath + item.path.replace(/::/g, '/') +
477                                '/' + itemTypes[myparent.ty] +
478                                '.' + myparent.name +
479                                '.html' + anchor;
480                     } else {
481                         displayPath = item.path + '::';
482                         href = rootPath + item.path.replace(/::/g, '/') +
483                                '/' + type + '.' + name + '.html';
484                     }
485
486                     output += '<tr class="' + type + ' result"><td>' +
487                               '<a href="' + href + '">' +
488                               displayPath + '<span class="' + type + '">' +
489                               name + '</span></a></td><td>' +
490                               '<a href="' + href + '">' +
491                               '<span class="desc">' + item.desc +
492                               '&nbsp;</span></a></td></tr>';
493                 });
494             } else {
495                 output += 'No results :( <a href="https://duckduckgo.com/?q=' +
496                     encodeURIComponent('rust ' + query.query) +
497                     '">Try on DuckDuckGo?</a>';
498             }
499
500             output += "</p>";
501             $('#main.content').addClass('hidden');
502             $('#search.content').removeClass('hidden').html(output);
503             $('#search .desc').width($('#search').width() - 40 -
504                 $('#search td:first-child').first().width());
505             initSearchNav();
506         }
507
508         function search(e) {
509             var query,
510                 filterdata = [],
511                 obj, i, len,
512                 results = [],
513                 maxResults = 200,
514                 resultIndex;
515             var params = getQueryStringParams();
516
517             query = getQuery();
518             if (e) {
519                 e.preventDefault();
520             }
521
522             if (!query.query || query.id === currentResults) {
523                 return;
524             }
525
526             // Because searching is incremental by character, only the most
527             // recent search query is added to the browser history.
528             if (browserSupportsHistoryApi()) {
529                 if (!history.state && !params.search) {
530                     history.pushState(query, "", "?search=" +
531                                                 encodeURIComponent(query.raw));
532                 } else {
533                     history.replaceState(query, "", "?search=" +
534                                                 encodeURIComponent(query.raw));
535                 }
536             }
537
538             resultIndex = execQuery(query, 20000, index);
539             len = resultIndex.length;
540             for (i = 0; i < len; ++i) {
541                 if (resultIndex[i].id > -1) {
542                     obj = searchIndex[resultIndex[i].id];
543                     filterdata.push([obj.name, obj.ty, obj.path, obj.desc]);
544                     results.push(obj);
545                 }
546                 if (results.length >= maxResults) {
547                     break;
548                 }
549             }
550
551             showResults(results);
552         }
553
554         // This mapping table should match the discriminants of
555         // `rustdoc::html::item_type::ItemType` type in Rust.
556         var itemTypes = ["mod",
557                          "struct",
558                          "type",
559                          "fn",
560                          "type",
561                          "static",
562                          "trait",
563                          "impl",
564                          "viewitem",
565                          "tymethod",
566                          "method",
567                          "structfield",
568                          "variant",
569                          "ffi",
570                          "ffs",
571                          "macro",
572                          "primitive"];
573
574         function itemTypeFromName(typename) {
575             for (var i = 0; i < itemTypes.length; ++i) {
576                 if (itemTypes[i] === typename) return i;
577             }
578             return -1;
579         }
580
581         function buildIndex(rawSearchIndex) {
582             searchIndex = [];
583             var searchWords = [];
584             for (var crate in rawSearchIndex) {
585                 if (!rawSearchIndex.hasOwnProperty(crate)) { continue }
586
587                 // an array of [(Number) item type,
588                 //              (String) name,
589                 //              (String) full path or empty string for previous path,
590                 //              (String) description,
591                 //              (optional Number) the parent path index to `paths`]
592                 var items = rawSearchIndex[crate].items;
593                 // an array of [(Number) item type,
594                 //              (String) name]
595                 var paths = rawSearchIndex[crate].paths;
596
597                 // convert `paths` into an object form
598                 var len = paths.length;
599                 for (var i = 0; i < len; ++i) {
600                     paths[i] = {ty: paths[i][0], name: paths[i][1]};
601                 }
602
603                 // convert `items` into an object form, and construct word indices.
604                 //
605                 // before any analysis is performed lets gather the search terms to
606                 // search against apart from the rest of the data.  This is a quick
607                 // operation that is cached for the life of the page state so that
608                 // all other search operations have access to this cached data for
609                 // faster analysis operations
610                 var len = items.length;
611                 var lastPath = "";
612                 for (var i = 0; i < len; ++i) {
613                     var rawRow = items[i];
614                     var row = {crate: crate, ty: rawRow[0], name: rawRow[1],
615                                path: rawRow[2] || lastPath, desc: rawRow[3],
616                                parent: paths[rawRow[4]]};
617                     searchIndex.push(row);
618                     if (typeof row.name === "string") {
619                         var word = row.name.toLowerCase();
620                         searchWords.push(word);
621                     } else {
622                         searchWords.push("");
623                     }
624                     lastPath = row.path;
625                 }
626             }
627             return searchWords;
628         }
629
630         function startSearch() {
631             var keyUpTimeout;
632             $('.do-search').on('click', search);
633             $('.search-input').on('keyup', function() {
634                 clearTimeout(keyUpTimeout);
635                 keyUpTimeout = setTimeout(search, 100);
636             });
637
638             // Push and pop states are used to add search results to the browser
639             // history.
640             if (browserSupportsHistoryApi()) {
641                 $(window).on('popstate', function(e) {
642                     var params = getQueryStringParams();
643                     // When browsing back from search results the main page
644                     // visibility must be reset.
645                     if (!params.search) {
646                         $('#main.content').removeClass('hidden');
647                         $('#search.content').addClass('hidden');
648                     }
649                     // When browsing forward to search results the previous
650                     // search will be repeated, so the currentResults are
651                     // cleared to ensure the search is successful.
652                     currentResults = null;
653                     // Synchronize search bar with query string state and
654                     // perform the search. This will empty the bar if there's
655                     // nothing there, which lets you really go back to a
656                     // previous state with nothing in the bar.
657                     $('.search-input').val(params.search);
658                     // Some browsers fire 'onpopstate' for every page load
659                     // (Chrome), while others fire the event only when actually
660                     // popping a state (Firefox), which is why search() is
661                     // called both here and at the end of the startSearch()
662                     // function.
663                     search();
664                 });
665             }
666             search();
667         }
668
669         index = buildIndex(rawSearchIndex);
670         startSearch();
671
672         // Draw a convenient sidebar of known crates if we have a listing
673         if (rootPath == '../') {
674             var sidebar = $('.sidebar');
675             var div = $('<div>').attr('class', 'block crate');
676             div.append($('<h2>').text('Crates'));
677
678             var crates = [];
679             for (var crate in rawSearchIndex) {
680                 if (!rawSearchIndex.hasOwnProperty(crate)) { continue }
681                 crates.push(crate);
682             }
683             crates.sort();
684             for (var i = 0; i < crates.length; ++i) {
685                 var klass = 'crate';
686                 if (crates[i] == window.currentCrate) {
687                     klass += ' current';
688                 }
689                 div.append($('<a>', {'href': '../' + crates[i] + '/index.html',
690                                     'class': klass}).text(crates[i]));
691             }
692             sidebar.append(div);
693         }
694     }
695
696     window.initSearch = initSearch;
697
698     window.register_implementors = function(imp) {
699         var list = $('#implementors-list');
700         var libs = Object.getOwnPropertyNames(imp);
701         for (var i = 0; i < libs.length; ++i) {
702             if (libs[i] == currentCrate) continue;
703             var structs = imp[libs[i]];
704             for (var j = 0; j < structs.length; ++j) {
705                 var code = $('<code>').append(structs[j]);
706                 $.each(code.find('a'), function(idx, a) {
707                     var href = $(a).attr('href');
708                     if (!href.startsWith('http')) {
709                         $(a).attr('href', rootPath + $(a).attr('href'));
710                     }
711                 });
712                 var li = $('<li>').append(code);
713                 list.append(li);
714             }
715         }
716     };
717     if (window.pending_implementors) {
718         window.register_implementors(window.pending_implementors);
719     }
720
721     // See documentation in html/render.rs for what this is doing.
722     var query = getQueryStringParams();
723     if (query['gotosrc']) {
724         window.location = $('#src-' + query['gotosrc']).attr('href');
725     }
726
727     $("#expand-all").on("click", function() {
728         $(".docblock").show();
729         $(".toggle-label").hide();
730         $(".toggle-wrapper").removeClass("collapsed");
731         $(".collapse-toggle").children(".inner").html("-");
732     });
733
734     $("#collapse-all").on("click", function() {
735         $(".docblock").hide();
736         $(".toggle-label").show();
737         $(".toggle-wrapper").addClass("collapsed");
738         $(".collapse-toggle").children(".inner").html("+");
739     });
740
741     $(document).on("click", ".collapse-toggle", function() {
742         var toggle = $(this);
743         var relatedDoc = toggle.parent().next();
744         if (relatedDoc.is(".docblock")) {
745             if (relatedDoc.is(":visible")) {
746                 relatedDoc.slideUp({duration:'fast', easing:'linear'});
747                 toggle.parent(".toggle-wrapper").addClass("collapsed");
748                 toggle.children(".inner").html("+");
749                 toggle.children(".toggle-label").fadeIn();
750             } else {
751                 relatedDoc.slideDown({duration:'fast', easing:'linear'});
752                 toggle.parent(".toggle-wrapper").removeClass("collapsed");
753                 toggle.children(".inner").html("-");
754                 toggle.children(".toggle-label").hide();
755             }
756         }
757     });
758
759     $(function() {
760         var toggle = $("<a/>", {'href': 'javascript:void(0)', 'class': 'collapse-toggle'})
761             .html("[<span class='inner'>-</span>]");
762
763         $(".method").each(function() {
764            if ($(this).next().is(".docblock")) {
765                $(this).children().first().after(toggle[0]);
766            }
767         });
768
769         var mainToggle =
770             $(toggle).append(
771                 $('<span/>', {'class': 'toggle-label'})
772                     .css('display', 'none')
773                     .html('&nbsp;Expand&nbsp;description'));
774         var wrapper =  $("<div class='toggle-wrapper'>").append(mainToggle);
775         $("#main > .docblock").before(wrapper);
776     });
777
778 }());