nitg-s: remove partial_types
[nit.git] / share / nitdoc / scripts / Nitdoc.QuickSearch.js
1 /* This file is part of NIT ( http://www.nitlanguage.org ).
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14
15 Documentation generator for the nit language.
16 Generate API documentation in HTML format from nit source code.
17 */
18
19 var Nitdoc = Nitdoc || {};
20
21 /*
22 * Nitdoc QuickSearch module
23 *
24 */
25
26 Nitdoc.QuickSearch = function() {
27 var rawList = nitdocQuickSearchRawList; // List of raw resulsts generated by nitdoc tool
28 var searchField = null; // <input:text> search field
29 var currentTable = null; // current search results <table>
30 var currentIndex = -1; // current cursor position into search results table
31
32 // Enable QuickSearch plugin
33 var enableQuickSearch = function(containerSelector) {
34 searchField = $(document.createElement("input"))
35 .attr({
36 id: "nitdoc-qs-field",
37 type: "text",
38 autocomplete: "off",
39 value: "quick search..."
40 })
41 .addClass("nitdoc-qs-field-notused")
42 .keydown(function(event) {
43 return Nitdoc.QuickSearch.doKeyDownAction(event.keyCode);
44 })
45 .keyup(function(event) {
46 Nitdoc.QuickSearch.doKeyUpAction(event.keyCode);
47 })
48 .focusout(function() {
49 if($(this).val() == "") {
50 $(this).addClass("nitdoc-qs-field-notused");
51 $(this).val("quick search...");
52 }
53 })
54 .focusin(function() {
55 if($(this).val() == "quick search...") {
56 $(this).removeClass("nitdoc-qs-field-notused");
57 $(this).val("");
58 }
59 });
60
61 $(containerSelector).append(
62 $(document.createElement("li"))
63 .attr("id", "nitdoc-qs-li")
64 .append(searchField)
65 );
66
67 // Close quicksearch list on click
68 $(document).click(function(e) {
69 Nitdoc.QuickSearch.closeResultsTable();
70 });
71 }
72
73 var doKeyDownAction = function(key) {
74 switch(key) {
75 case 38: // Up
76 selectPrevResult();
77 return false;
78 case 40: // Down
79 selectNextResult();
80 return false;
81 default:
82 return true;
83 }
84 }
85
86 var doKeyUpAction = function(key) {
87 switch(key) {
88 case 38: // Up
89 case 40: // Down
90 break;
91
92 case 13: // Enter
93 goToResult();
94 return false;
95 break;
96
97 case 27: // Escape
98 $(this).blur();
99 closeResultsTable();
100 break;
101
102 default: // Other keys
103 var query = searchField.val();
104 if(!query) {
105 return false;
106 }
107 var results = getResults(query);
108 displayResultsTable(query, results);
109 break;
110 }
111 }
112
113 // Get results corresponding to search query
114 var getResults = function(query) {
115 var results = {};
116 results.matches = new Array();
117 for(var entry in rawList) {
118 if(!entry.startsWith(query, true)) {
119 continue;
120 }
121 var cat = new Object();
122 cat.name = entry;
123 cat.entries = rawList[entry];
124 results.matches[results.matches.length] = cat;
125
126 if(entry == query) {
127 cat.rank = 3;
128 } else if(entry.toUpperCase() == query.toUpperCase()) {
129 cat.rank = 2;
130 } else {
131 cat.rank = 1 + query.dice(entry);
132 }
133 }
134 results.matches.sort(rankSorter);
135 results.partials = new Array();
136 if(results.matches.length == 0) {
137 for(var entry in rawList) {
138 var cat = new Object();
139 cat.name = entry;
140 cat.entries = rawList[entry];
141 cat.rank = query.dice(entry);
142 if(cat.rank > 0) {
143 results.partials[results.partials.length] = cat;
144 }
145 }
146 results.partials.sort(rankSorter);
147 }
148 return results;
149 }
150
151 // Sort an array of results by rank
152 var rankSorter = function(a, b){
153 if(a.rank < b.rank) {
154 return 1;
155 } else if(a.rank > b.rank) {
156 return -1;
157 }
158 return 0;
159 }
160
161 // Display results in a popup table
162 var displayResultsTable = function(query, results) {
163 // Clear results table
164 if(currentTable) currentTable.remove();
165
166 // Build results table
167 currentIndex = -1;
168 currentTable = $(document.createElement("table"));
169 currentTable.attr("id", "nitdoc-qs-table");
170 currentTable.css("position", "absolute");
171 currentTable.width(searchField.outerWidth());
172
173 var maxSize = 10;
174 var count = 0;
175 var resultSet;
176 if(results.matches.length == 0) {
177 resultSet = results.partials
178 } else {
179 resultSet = results.matches
180 }
181 for(var i in resultSet) {
182 var cat = resultSet[i];
183 var result = cat.entries[0];
184
185 addResultRow(count, cat.name, result.txt, result.url, "nitdoc-qs-cat")
186 if(count >= maxSize) {
187 currentTable.find("tbody").children().last().hide();
188 }
189 count++;
190
191 for(var j = 1; j < cat.entries.length; j++) {
192 var result = cat.entries[j];
193 addResultRow(count, cat.name, result.txt, result.url, "nitdoc-qs-sub")
194 if(count >= maxSize) {
195 currentTable.find("tr.nitdoc-qs-row").last().hide();
196 }
197 count++;
198 }
199 }
200 if(count >= maxSize) {
201 currentTable.prepend(
202 $(document.createElement("tr"))
203 .addClass("nitdoc-qs-overflow-up")
204 .addClass("nitdoc-qs-overflow-inactive")
205 .append(
206 $(document.createElement("td"))
207 .attr("colspan", 2)
208 .html("&#x25B2;")
209 )
210 .click( function(e) {
211 e.stopPropagation();
212 selectPrevResult();
213 })
214 );
215 currentTable.append(
216 $(document.createElement("tr"))
217 .addClass("nitdoc-qs-overflow-down")
218 .addClass(count >= maxSize ? "nitdoc-qs-overflow-active" : "nitdoc-qs-overflow-inactive")
219 .append(
220 $(document.createElement("td"))
221 .attr("colspan", 2)
222 .html("&#x25BC;")
223 )
224 .click( function(e) {
225 e.stopPropagation();
226 console.log("nest");
227 selectNextResult();
228 })
229 );
230 }
231 if(results.matches.length == 0) {
232 currentTable.prepend(
233 $("<tr class='nitdoc-qs-noresult'>")
234 .append(
235 $("<td colspan='2'>")
236 .html("Sorry, there is no match, best results are:")
237 )
238 );
239 }
240
241 // Initialize table
242 $("body").append(currentTable);
243 resizeResultsTable();
244 if(currentTable.find("tr").length > 0) {
245 setIndex(0);
246 }
247 }
248
249 // adds a result row to the current result table
250 var addResultRow = function(index, name, txt, url, cls) {
251 currentTable.append(
252 $(document.createElement("tr"))
253 .addClass("nitdoc-qs-row")
254 .data("searchDetails", {name: name, url: url})
255 .data("index", index)
256 .append(
257 $(document.createElement("td")).html(name)
258 .addClass(cls)
259 )
260 .append(
261 $(document.createElement("td"))
262 .addClass("nitdoc-qs-info")
263 .html(txt + "&nbsp;&raquo;")
264 )
265 .mouseover( function() {
266 setIndex($(this).data("index"));
267 })
268 .mouseout( function() {
269 $(this).removeClass("nitdoc-qs-active");
270 })
271 .click( function() {
272 window.location = $(this).data("searchDetails")["url"];
273 })
274 );
275 }
276
277 // adapts result table to content
278 var resizeResultsTable = function() {
279 currentTable.offset({
280 left: searchField.offset().left + (searchField.outerWidth() - currentTable.outerWidth()),
281 top: searchField.offset().top + searchField.outerHeight()
282 });
283 }
284
285 // select row at index
286 var setIndex = function(index) {
287 $(currentTable.find("tr.nitdoc-qs-row")[currentIndex]).removeClass("nitdoc-qs-active");
288 currentIndex = index;
289 var currentRow = $(currentTable.find("tr.nitdoc-qs-row")[currentIndex]);
290 currentRow.addClass("nitdoc-qs-active");
291 //searchField.val(currentRow.data("searchDetails").name);
292 }
293
294 var hasPrev = function(index) {
295 return index - 1 >= 0;
296 }
297
298 var hasNext = function(index) {
299 return index + 1 < currentTable.find("tr.nitdoc-qs-row").length;
300 }
301
302 var selectPrevResult = function() {
303 if(hasPrev(currentIndex)) {
304 setIndex(currentIndex - 1);
305 if(!$(currentTable.find("tr.nitdoc-qs-row")[currentIndex]).is(":visible")) {
306 currentTable.find("tr.nitdoc-qs-row:visible").last().hide();
307 currentTable.find("tr.nitdoc-qs-overflow-down").addClass("nitdoc-qs-overflow-active");
308 $(currentTable.find("tr.nitdoc-qs-row")[currentIndex]).show();
309 if(!hasPrev(currentIndex)) {
310 currentTable.find("tr.nitdoc-qs-overflow-up").removeClass("nitdoc-qs-overflow-active");
311 }
312 resizeResultsTable();
313 }
314 }
315 }
316
317 var selectNextResult = function() {
318 if(hasNext(currentIndex)) {
319 setIndex(currentIndex + 1);
320 if(!$(currentTable.find("tr.nitdoc-qs-row")[currentIndex]).is(":visible")) {
321 currentTable.find("tr.nitdoc-qs-row:visible").first().hide();
322 currentTable.find("tr.nitdoc-qs-overflow-up").addClass("nitdoc-qs-overflow-active");
323 $(currentTable.find("tr.nitdoc-qs-row")[currentIndex]).show();
324 if(!hasNext(currentIndex)) {
325 currentTable.find("tr.nitdoc-qs-overflow-down").removeClass("nitdoc-qs-overflow-active");
326 }
327 resizeResultsTable();
328 }
329 }
330 }
331
332 // Load selected search result page
333 var goToResult = function() {
334 if(currentIndex > -1) {
335 window.location = $(currentTable.find("tr.nitdoc-qs-row")[currentIndex]).data("searchDetails").url;
336 return;
337 }
338
339 if(searchField.val().length == 0) { return; }
340
341 window.location = "search.html#q=" + searchField.val();
342 if(window.location.href.indexOf("search.html") > -1) {
343 location.reload();
344 }
345 }
346
347 // Close the results table
348 closeResultsTable = function(target) {
349 if(target != searchField && target != currentTable) {
350 if(currentTable != null) {
351 currentTable.remove();
352 currentTable = null;
353 }
354 }
355 }
356
357 // Public interface
358 var quicksearch = {
359 enableQuickSearch: enableQuickSearch,
360 doKeyUpAction: doKeyUpAction,
361 doKeyDownAction: doKeyDownAction,
362 closeResultsTable: closeResultsTable
363 };
364
365 return quicksearch;
366 }();
367
368 $(document).ready(function() {
369 Nitdoc.QuickSearch.enableQuickSearch("nav.main ul");
370 });
371
372 /*
373 * Utils
374 */
375
376 String.prototype.startsWith = function(prefix, caseSensitive) {
377 if(caseSensitive) {
378 return this.toUpperCase().indexOf(prefix.toUpperCase()) === 0;
379 }
380 return this.indexOf(prefix) === 0;
381 }
382
383 // Compare two strings using Sorensen-Dice Coefficient
384 // see: http://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient
385 String.prototype.dice = function(other) {
386 var length1 = this.length - 1;
387 var length2 = other.length - 1;
388 if(length1 < 1 || length2 < 1) return 0;
389
390 var bigrams2 = [];
391 for(var i = 0; i < length2; i++) {
392 bigrams2.push(other.substr(i, 2));
393 }
394
395 var intersection = 0;
396 for(var i = 0; i < length1; i++) {
397 var bigram1 = this.substr(i, 2);
398 for(var j = 0; j < length2; j++) {
399 if(bigram1 == bigrams2[j]) {
400 intersection++;
401 bigrams2[j] = null;
402 break;
403 }
404 }
405 }
406 return (2.0 * intersection) / (length1 + length2);
407 }
408