]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/static/main.js
auto merge of #11598 : alexcrichton/rust/io-export, r=brson
[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, searchIndex: true, rootPath: true, allPaths: true */
13
14 (function() {
15     "use strict";
16     var resizeTimeout, interval;
17
18     $('.js-only').removeClass('js-only');
19
20     function resizeShortBlocks() {
21         if (resizeTimeout) {
22             clearTimeout(resizeTimeout);
23         }
24         resizeTimeout = setTimeout(function() {
25             var contentWidth = $('.content').width();
26             $('.docblock.short').width(function() {
27                 return contentWidth - 40 - $(this).prev().width();
28             }).addClass('nowrap');
29         }, 150);
30     }
31     resizeShortBlocks();
32     $(window).on('resize', resizeShortBlocks);
33
34     function highlightSourceLines() {
35         var i, from, to, match = window.location.hash.match(/^#?(\d+)(?:-(\d+))?$/);
36         if (match) {
37             from = parseInt(match[1], 10);
38             to = Math.min(50000, parseInt(match[2] || match[1], 10));
39             from = Math.min(from, to);
40             if ($('#' + from).length === 0) {
41                 return;
42             }
43             $('#' + from)[0].scrollIntoView();
44             $('.line-numbers span').removeClass('line-highlighted');
45             for (i = from; i <= to; i += 1) {
46                 $('#' + i).addClass('line-highlighted');
47             }
48         }
49     }
50     highlightSourceLines();
51     $(window).on('hashchange', highlightSourceLines);
52
53     $(document).on('keyup', function(e) {
54         if (document.activeElement.tagName === 'INPUT') {
55             return;
56         }
57
58         if (e.keyCode === 188 && $('#help').hasClass('hidden')) { // question mark
59             e.preventDefault();
60             $('#help').removeClass('hidden');
61         } else if (e.keyCode === 27) { // esc
62             if (!$('#help').hasClass('hidden')) {
63                 e.preventDefault();
64                 $('#help').addClass('hidden');
65             } else if (!$('#search').hasClass('hidden')) {
66                 e.preventDefault();
67                 $('#search').addClass('hidden');
68                 $('#main').removeClass('hidden');
69             }
70         } else if (e.keyCode === 83) { // S
71             e.preventDefault();
72             $('.search-input').focus();
73         }
74     }).on('click', function(e) {
75         if (!$(e.target).closest('#help').length) {
76             $('#help').addClass('hidden');
77         }
78     });
79
80     $('.version-selector').on('change', function() {
81         var i, match,
82             url = document.location.href,
83             stripped = '',
84             len = rootPath.match(/\.\.\//g).length + 1;
85
86         for (i = 0; i < len; i += 1) {
87             match = url.match(/\/[^\/]*$/);
88             if (i < len - 1) {
89                 stripped = match[0] + stripped;
90             }
91             url = url.substring(0, url.length - match[0].length);
92         }
93
94         url += '/' + $('.version-selector').val() + stripped;
95
96         document.location.href = url;
97     });
98
99     function initSearch(searchIndex) {
100         var currentResults, index;
101
102         // clear cached values from the search bar
103         $(".search-input")[0].value = '';
104
105         /**
106          * Executes the query and builds an index of results
107          * @param  {[Object]} query     [The user query]
108          * @param  {[type]} max         [The maximum results returned]
109          * @param  {[type]} searchWords [The list of search words to query against]
110          * @return {[type]}             [A search index of results]
111          */
112         function execQuery(query, max, searchWords) {
113             var valLower = query.query.toLowerCase(),
114                 val = valLower,
115                 typeFilter = query.type,
116                 results = [],
117                 aa = 0,
118                 bb = 0,
119                 split = valLower.split("::");
120
121             //remove empty keywords
122             for (var j = 0; j < split.length; j++) {
123                 split[j].toLowerCase();
124                 if (split[j] === "") {
125                     split.splice(j, 1);
126                 }
127             }
128
129             // quoted values mean literal search
130             bb = searchWords.length;
131             if ((val.charAt(0) === "\"" || val.charAt(0) === "'") && val.charAt(val.length - 1) === val.charAt(0)) {
132                 val = val.substr(1, val.length - 2);
133                 for (aa = 0; aa < bb; aa += 1) {
134                     if (searchWords[aa] === val) {
135                         // filter type: ... queries
136                         if (!typeFilter || typeFilter === searchIndex[aa].ty) {
137                             results.push([aa, -1]);
138                         }
139                     }
140                     if (results.length === max) {
141                         break;
142                     }
143                 }
144             } else {
145                 // gather matching search results up to a certain maximum
146                 val = val.replace(/\_/g, "");
147                 for (var i = 0; i < split.length; i++) {
148                     for (aa = 0; aa < bb; aa += 1) {
149                         if (searchWords[aa].indexOf(split[i]) > -1 || searchWords[aa].indexOf(val) > -1 || searchWords[aa].replace(/_/g, "").indexOf(val) > -1) {
150                             // filter type: ... queries
151                             if (!typeFilter || typeFilter === searchIndex[aa].ty) {
152                                 results.push([aa, searchWords[aa].replace(/_/g, "").indexOf(val)]);
153                             }
154                         }
155                         if (results.length === max) {
156                             break;
157                         }
158                     }
159                 }
160             }
161
162             bb = results.length;
163             for (aa = 0; aa < bb; aa += 1) {
164                 results[aa].push(searchIndex[results[aa][0]].ty);
165                 results[aa].push(searchIndex[results[aa][0]].path);
166                 results[aa].push(searchIndex[results[aa][0]].name);
167                 results[aa].push(searchIndex[results[aa][0]].parent);
168             }
169             // if there are no results then return to default and fail
170             if (results.length === 0) {
171                 return [];
172             }
173
174             // sort by exact match
175             results.sort(function search_complete_sort0(aaa, bbb) {
176                 if (searchWords[aaa[0]] === valLower && searchWords[bbb[0]] !== valLower) {
177                     return 1;
178                 }
179             });
180             // first sorting attempt
181             // sort by item name length
182             results.sort(function search_complete_sort1(aaa, bbb) {
183                 if (searchWords[aaa[0]].length > searchWords[bbb[0]].length) {
184                     return 1;
185                 }
186             });
187             // second sorting attempt
188             // sort by item name
189             results.sort(function search_complete_sort1(aaa, bbb) {
190                 if (searchWords[aaa[0]].length === searchWords[bbb[0]].length && searchWords[aaa[0]] > searchWords[bbb[0]]) {
191                     return 1;
192                 }
193             });
194             // third sorting attempt
195             // sort by index of keyword in item name
196             if (results[0][1] !== -1) {
197                 results.sort(function search_complete_sort1(aaa, bbb) {
198                     if (aaa[1] > bbb[1] && bbb[1] === 0) {
199                         return 1;
200                     }
201                 });
202             }
203             // fourth sorting attempt
204             // sort by type
205             results.sort(function search_complete_sort3(aaa, bbb) {
206                 if (searchWords[aaa[0]] === searchWords[bbb[0]] && aaa[2] > bbb[2]) {
207                     return 1;
208                 }
209             });
210             // fifth sorting attempt
211             // sort by path
212             results.sort(function search_complete_sort4(aaa, bbb) {
213                 if (searchWords[aaa[0]] === searchWords[bbb[0]] && aaa[2] === bbb[2] && aaa[3] > bbb[3]) {
214                     return 1;
215                 }
216             });
217             // sixth sorting attempt
218             // remove duplicates, according to the data provided
219             for (aa = results.length - 1; aa > 0; aa -= 1) {
220                 if (searchWords[results[aa][0]] === searchWords[results[aa - 1][0]] && results[aa][2] === results[aa - 1][2] && results[aa][3] === results[aa - 1][3]) {
221                     results[aa][0] = -1;
222                 }
223             }
224             for (var i = 0; i < results.length; i++) {
225                 var result = results[i],
226                     name = result[4].toLowerCase(),
227                     path = result[3].toLowerCase(),
228                     parent = allPaths[result[5]];
229
230                 var valid = validateResult(name, path, split, parent);
231                 if (!valid) {
232                     result[0] = -1;
233                 }
234             }
235             return results;
236         }
237
238         /**
239          * Validate performs the following boolean logic. For example: "File::open" will give
240          * IF A PARENT EXISTS => ("file" && "open") exists in (name || path || parent)
241          * OR => ("file" && "open") exists in (name || path )
242          *
243          * This could be written functionally, but I wanted to minimise functions on stack.
244          * @param  {[string]} name   [The name of the result]
245          * @param  {[string]} path   [The path of the result]
246          * @param  {[string]} keys   [The keys to be used (["file", "open"])]
247          * @param  {[object]} parent [The parent of the result]
248          * @return {[boolean]}       [Whether the result is valid or not]
249          */
250         function validateResult(name, path, keys, parent) {
251             //initially valid
252             var validate = true;
253             //if there is a parent, then validate against parent
254             if (parent !== undefined) {
255                 for (var i = 0; i < keys.length; i++) {
256                     // if previous keys are valid and current key is in the path, name or parent
257                     if ((validate) && (name.toLowerCase().indexOf(keys[i]) > -1 || path.toLowerCase().indexOf(keys[i]) > -1 || parent.name.toLowerCase().indexOf(keys[i]) > -1)) {
258                         validate = true;
259                     } else {
260                         validate = false;
261                     }
262                 }
263             } else {
264                 for (var i = 0; i < keys.length; i++) {
265                     // if previous keys are valid and current key is in the path, name
266                     if ((validate) && (name.toLowerCase().indexOf(keys[i]) > -1 || path.toLowerCase().indexOf(keys[i]) > -1)) {
267                         validate = true;
268                     } else {
269                         validate = false;
270                     }
271                 }
272             }
273             return validate;
274         }
275
276         function getQuery() {
277             var matches, type, query = $('.search-input').val();
278
279             matches = query.match(/^(fn|mod|str(uct)?|enum|trait|t(ype)?d(ef)?)\s*:\s*/i);
280             if (matches) {
281                 type = matches[1].replace(/^td$/, 'typedef').replace(/^str$/, 'struct').replace(/^tdef$/, 'typedef').replace(/^typed$/, 'typedef');
282                 query = query.substring(matches[0].length);
283             }
284
285             return {
286                 query: query,
287                 type: type,
288                 id: query + type,
289             };
290         }
291
292         function initSearchNav() {
293             var hoverTimeout, $results = $('.search-results .result');
294
295             $results.on('click', function() {
296                 var dst = $(this).find('a')[0];
297                 console.log(window.location.pathname, dst.pathname);
298                 if (window.location.pathname == dst.pathname) {
299                     $('#search').addClass('hidden');
300                     $('#main').removeClass('hidden');
301                 }
302                 document.location.href = dst.href;
303             }).on('mouseover', function() {
304                 var $el = $(this);
305                 clearTimeout(hoverTimeout);
306                 hoverTimeout = setTimeout(function() {
307                     $results.removeClass('highlighted');
308                     $el.addClass('highlighted');
309                 }, 20);
310             });
311
312             $(document).off('keypress.searchnav');
313             $(document).on('keypress.searchnav', function(e) {
314                 var $active = $results.filter('.highlighted');
315
316                 if (e.keyCode === 38) { // up
317                     e.preventDefault();
318                     if (!$active.length || !$active.prev()) {
319                         return;
320                     }
321
322                     $active.prev().addClass('highlighted');
323                     $active.removeClass('highlighted');
324                 } else if (e.keyCode === 40) { // down
325                     e.preventDefault();
326                     if (!$active.length) {
327                         $results.first().addClass('highlighted');
328                     } else if ($active.next().length) {
329                         $active.next().addClass('highlighted');
330                         $active.removeClass('highlighted');
331                     }
332                 } else if (e.keyCode === 13) { // return
333                     e.preventDefault();
334                     if ($active.length) {
335                         document.location.href = $active.find('a').prop('href');
336                     }
337                 }
338             });
339         }
340
341         function showResults(results) {
342             var output, shown, query = getQuery();
343
344             currentResults = query.id;
345             output = '<h1>Results for ' + query.query + (query.type ? ' (type: ' + query.type + ')' : '') + '</h1>';
346             output += '<table class="search-results">';
347
348             if (results.length > 0) {
349                 shown = [];
350
351                 results.forEach(function(item) {
352                     var name, type;
353
354                     if (shown.indexOf(item) !== -1) {
355                         return;
356                     }
357
358                     shown.push(item);
359                     name = item.name;
360                     type = item.ty;
361
362                     output += '<tr class="' + type + ' result"><td>';
363
364                     if (type === 'mod') {
365                         output += item.path +
366                             '::<a href="' + rootPath +
367                             item.path.replace(/::/g, '/') + '/' +
368                             name + '/index.html" class="' +
369                             type + '">' + name + '</a>';
370                     } else if (type === 'static' || type === 'reexport') {
371                         output += item.path +
372                             '::<a href="' + rootPath +
373                             item.path.replace(/::/g, '/') +
374                             '/index.html" class="' + type +
375                             '">' + name + '</a>';
376                     } else if (item.parent !== undefined) {
377                         var myparent = allPaths[item.parent];
378                         var anchor = '#' + type + '.' + name;
379                         output += item.path + '::' + myparent.name +
380                             '::<a href="' + rootPath +
381                             item.path.replace(/::/g, '/') +
382                             '/' + myparent.type +
383                             '.' + myparent.name +
384                             '.html' + anchor +
385                             '" class="' + type +
386                             '">' + name + '</a>';
387                     } else {
388                         output += item.path +
389                             '::<a href="' + rootPath +
390                             item.path.replace(/::/g, '/') +
391                             '/' + type +
392                             '.' + name +
393                             '.html" class="' + type +
394                             '">' + name + '</a>';
395                     }
396
397                     output += '</td><td><span class="desc">' + item.desc +
398                         '</span></td></tr>';
399                 });
400             } else {
401                 output += 'No results :( <a href="https://duckduckgo.com/?q=' +
402                     encodeURIComponent('rust ' + query.query) +
403                     '">Try on DuckDuckGo?</a>';
404             }
405
406             output += "</p>";
407             $('#main.content').addClass('hidden');
408             $('#search.content').removeClass('hidden').html(output);
409             $('#search .desc').width($('#search').width() - 40 -
410                 $('#search td:first-child').first().width());
411             initSearchNav();
412         }
413
414         function search(e) {
415             var query,
416                 filterdata = [],
417                 obj, i, len,
418                 results = [],
419                 maxResults = 200,
420                 resultIndex;
421
422             query = getQuery();
423             if (e) {
424                 e.preventDefault();
425             }
426
427             if (!query.query || query.id === currentResults) {
428                 return;
429             }
430
431             resultIndex = execQuery(query, 20000, index);
432             len = resultIndex.length;
433             for (i = 0; i < len; i += 1) {
434                 if (resultIndex[i][0] > -1) {
435                     obj = searchIndex[resultIndex[i][0]];
436                     filterdata.push([obj.name, obj.ty, obj.path, obj.desc]);
437                     results.push(obj);
438                 }
439                 if (results.length >= maxResults) {
440                     break;
441                 }
442             }
443
444             // TODO add sorting capability through this function?
445             //
446             //            // the handler for the table heading filtering
447             //            filterdraw = function search_complete_filterdraw(node) {
448             //                var name = "",
449             //                    arrow = "",
450             //                    op = 0,
451             //                    tbody = node.parentNode.parentNode.nextSibling,
452             //                    anchora = {},
453             //                    tra = {},
454             //                    tha = {},
455             //                    td1a = {},
456             //                    td2a = {},
457             //                    td3a = {},
458             //                    aaa = 0,
459             //                    bbb = 0;
460             //
461             //                // the 4 following conditions set the rules for each
462             //                // table heading
463             //                if (node === ths[0]) {
464             //                    op = 0;
465             //                    name = "name";
466             //                    ths[1].innerHTML = ths[1].innerHTML.split(" ")[0];
467             //                    ths[2].innerHTML = ths[2].innerHTML.split(" ")[0];
468             //                    ths[3].innerHTML = ths[3].innerHTML.split(" ")[0];
469             //                }
470             //                if (node === ths[1]) {
471             //                    op = 1;
472             //                    name = "type";
473             //                    ths[0].innerHTML = ths[0].innerHTML.split(" ")[0];
474             //                    ths[2].innerHTML = ths[2].innerHTML.split(" ")[0];
475             //                    ths[3].innerHTML = ths[3].innerHTML.split(" ")[0];
476             //                }
477             //                if (node === ths[2]) {
478             //                    op = 2;
479             //                    name = "path";
480             //                    ths[0].innerHTML = ths[0].innerHTML.split(" ")[0];
481             //                    ths[1].innerHTML = ths[1].innerHTML.split(" ")[0];
482             //                    ths[3].innerHTML = ths[3].innerHTML.split(" ")[0];
483             //                }
484             //                if (node === ths[3]) {
485             //                    op = 3;
486             //                    name = "description";
487             //                    ths[0].innerHTML = ths[0].innerHTML.split(" ")[0];
488             //                    ths[1].innerHTML = ths[1].innerHTML.split(" ")[0];
489             //                    ths[2].innerHTML = ths[2].innerHTML.split(" ")[0];
490             //                }
491             //
492             //                // ascending or descending search
493             //                arrow = node.innerHTML.split(" ")[1];
494             //                if (arrow === undefined || arrow === "\u25b2") {
495             //                    arrow = "\u25bc";
496             //                } else {
497             //                    arrow = "\u25b2";
498             //                }
499             //
500             //                // filter the data
501             //                filterdata.sort(function search_complete_filterDraw_sort(xx, yy) {
502             //                    if ((arrow === "\u25b2" && xx[op].toLowerCase() < yy[op].toLowerCase()) || (arrow === "\u25bc" && xx[op].toLowerCase() > yy[op].toLowerCase())) {
503             //                        return 1;
504             //                    }
505             //                });
506             //            };
507
508             showResults(results);
509         }
510
511         function buildIndex(searchIndex) {
512             var len = searchIndex.length,
513                 i = 0,
514                 searchWords = [];
515
516             // before any analysis is performed lets gather the search terms to
517             // search against apart from the rest of the data.  This is a quick
518             // operation that is cached for the life of the page state so that
519             // all other search operations have access to this cached data for
520             // faster analysis operations
521             for (i = 0; i < len; i += 1) {
522                 if (typeof searchIndex[i].name === "string") {
523                     searchWords.push(searchIndex[i].name.toLowerCase());
524                 } else {
525                     searchWords.push("");
526                 }
527             }
528
529             return searchWords;
530         }
531
532         function startSearch() {
533             var keyUpTimeout;
534             $('.do-search').on('click', search);
535             $('.search-input').on('keyup', function() {
536                 clearTimeout(keyUpTimeout);
537                 keyUpTimeout = setTimeout(search, 100);
538             });
539         }
540
541         index = buildIndex(searchIndex);
542         startSearch();
543     }
544
545     initSearch(searchIndex);
546 }());