5 // Current search results preview table
6 var currentTable
= null;
8 //Hightlighted index in search result preview table
11 $
(document
).ready(function() {
13 // Insert search field
16 $
(document
.createElement("li"))
18 $
(document
.createElement("form"))
20 $
(document
.createElement("input"))
25 value
: "quick search..."
34 Nitdoc
.QuickSearch
.selectPrevResult();
39 Nitdoc
.QuickSearch
.selectNextResult();
43 Nitdoc
.QuickSearch
.goToResult();
50 Nitdoc
.QuickSearch
.close();
55 var query
= $
("#search").val();
59 var results
= Nitdoc
.QuickSearch
.search(query
);
60 Nitdoc
.QuickSearch
.showResults(query
, results
);
64 .focusout(function() {
65 if($
(this).val() == "") {
66 $
(this).addClass("notUsed");
67 $
(this).val("quick search...");
71 if($
(this).val() == "quick search...") {
72 $
(this).removeClass("notUsed");
83 // Close quicksearch list on click
84 $
(document
).click(function(e
) {
85 Nitdoc
.QuickSearch
.close();
90 Nitdoc
.QuickSearch
= {};
93 Nitdoc
.QuickSearch
.search
= function(query
) {
94 // Escape regexp related characters in query
95 query
= query
.replace(/\\/gi
, "\\\\");
96 query
= query
.replace(/\
[/gi
, "\\[");
97 query
= query
.replace(/\|
/gi
, "\\|");
98 query
= query
.replace(/\*/gi
, "\\*");
99 query
= query
.replace(/\
+/gi
, "\\+");
100 query
= query
.replace(/\?/gi
, "\\?");
101 query
= query
.replace(/\
(/gi
, "\\(");
102 query
= query
.replace(/\
)/gi
, "\\)");
103 query
= query
.replace(/&/gi
, "&&");
104 // Look for matches in JSON entries
105 var results
= new Array();
106 var regexp
= new RegExp("^" + query
, "i");
107 for(var entry
in entries
) {
108 for(var i
in entries
[entry]) {
109 var result
= entries
[entry][i];
110 result
.entry
= entry
;
111 result
.distance
= query
.dice(entry
);
112 results
[results
.length
] = result
;
115 results
.sort(Nitdoc
.QuickSearch
.resultSorter
);
119 // Sort an array of results
120 Nitdoc
.QuickSearch
.resultSorter
= function(a
, b
){
121 if(a
.distance
< b
.distance
) {
123 } else if(a
.distance
> b
.distance
) {
129 // Display resulsts in popup table
130 Nitdoc
.QuickSearch
.showResults
= function(query
, results
) {
131 // Remove previous table
132 if(currentTable
!= null) {
133 currentTable
.remove();
135 // Build results table
137 currentTable
= $
(document
.createElement("table"));
139 for(var i
in results
) {
144 var result
= results
[i];
146 $
(document
.createElement("tr"))
147 .data("searchDetails", {name
: result
.entry
, url
: result
.url
})
149 .append($
(document
.createElement("td")).html(result
.entry
))
151 $
(document
.createElement("td"))
152 .addClass("entryInfo")
153 .html(result
.txt
+ " »")
155 .mouseover( function() {
156 $
(currentTable
.find("tr")[currentIndex]).removeClass("activeSearchResult");
157 $
(this).addClass("activeSearchResult");
158 currentIndex
= $
(this).data("index");
160 .mouseout( function() {
161 $
(this).removeClass("activeSearchResult");
164 window
.location
= $
(this).data("searchDetails")["url"];
170 $
("<tr class='overflow'>")
172 $
("<td colspan='2'>")
174 $
("<a href='#' title='Show all results' data-query='"+ query
+"'>" + overflow
+ " more results for '" + query
+ "'</a>")
176 window
.location
= "search.html#q=" + $
(this).attr("data-query");
177 if(window
.location
.href
.indexOf("search.html") > -1) {
185 // Initialize table properties
186 currentTable
.attr("id", "searchTable");
187 currentTable
.css("position", "absolute");
188 currentTable
.width($
("#search").outerWidth());
189 $
("body").append(currentTable
);
190 currentTable
.offset({left
: $
("#search").offset().left
+ ($
("#search").outerWidth() - currentTable
.outerWidth()), top
: $
("#search").offset().top
+ $
("#search").outerHeight()});
191 // Preselect first entry
192 if(currentTable
.find("tr").length
> 0) {
194 $
(currentTable
.find("tr")[currentIndex]).addClass("activeSearchResult");
195 $
("#search").focus();
199 // Select the previous result
200 Nitdoc
.QuickSearch
.selectPrevResult
= function() {
201 // If already on first result, focus search input
202 if(currentIndex
== 0) {
203 $
("#search").val($
(currentTable
.find("tr")[currentIndex]).data("searchDetails").name
);
204 $
("#search").focus();
205 // Else select previous result
206 } else if(currentIndex
> 0) {
207 $
(currentTable
.find("tr")[currentIndex]).removeClass("activeSearchResult");
209 $
(currentTable
.find("tr")[currentIndex]).addClass("activeSearchResult");
210 $
("#search").val($
(currentTable
.find("tr")[currentIndex]).data("searchDetails").name
);
211 $
("#search").focus();
215 // Select the next result
216 Nitdoc
.QuickSearch
.selectNextResult
= function() {
217 if(currentIndex
< currentTable
.find("tr").length
- 1) {
218 if($
(currentTable
.find("tr")[currentIndex
+ 1]).hasClass("overflow")) {
221 $
(currentTable
.find("tr")[currentIndex]).removeClass("activeSearchResult");
223 $
(currentTable
.find("tr")[currentIndex]).addClass("activeSearchResult");
224 $
("#search").val($
(currentTable
.find("tr")[currentIndex]).data("searchDetails").name
);
225 $
("#search").focus();
229 // Load selected search result
230 Nitdoc
.QuickSearch
.goToResult
= function() {
231 if(currentIndex
> -1) {
232 window
.location
= $
(currentTable
.find("tr")[currentIndex]).data("searchDetails").url
;
236 if($
("#search").val().length
== 0) { return; }
238 window
.location
= "search.html#q=" + $
("#search").val();
239 if(window
.location
.href
.indexOf("search.html") > -1) {
244 // Close the QuickSearch results
245 Nitdoc
.QuickSearch
.close
= function(target
) {
246 if(target
!= $
("#search")[0] && target
!= $
("#searchTable")[0]) {
247 if(currentTable
!= null) {
248 currentTable
.remove();
256 // Calculate levenshtein distance beetween two strings
257 // see: http://en.wikipedia.org/wiki/Levenshtein_distance
258 String
.prototype.levenshtein
= function(other
) {
259 var matrix
= new Array();
261 for(var i
= 0; i
<= this.length
; i
++) {
262 matrix
[i] = new Array();
265 for(var j
= 0; j
<= other
.length
; j
++) {
269 for(var i
= 1; i
<= this.length
; i
++) {
270 for(var j
= 1; j
<= other
.length
; j
++) {
271 if(this.charAt(i
- 1) == other
.charAt(j
- 1)) {
273 } else if(this.charAt(i
- 1).toLowerCase() == other
.charAt(j
- 1).toLowerCase()) {
278 matrix
[i][j] = Math
.min(
279 matrix
[i
- 1][j] + 1, // deletion
280 matrix
[i][j
- 1] + 1, // insertion
281 matrix
[i
- 1][j
- 1] + cost
// substitution
285 return matrix
[this.length
][other
.length
]
288 // Compare two strings using Sorensen-Dice Coefficient
289 // see: http://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient
290 String
.prototype.dice
= function(other
) {
291 var length1
= this.length
- 1;
292 var length2
= other
.length
- 1;
293 if(length1
< 1 || length2
< 1) return 0;
296 for(var i
= 0; i
< length2
; i
++) {
297 bigrams2
.push(other
.substr(i
, 2));
300 var intersection
= 0;
301 for(var i
= 0; i
< length1
; i
++) {
302 var bigram1
= this.substr(i
, 2);
303 for(var j
= 0; j
< length2
; j
++) {
304 if(bigram1
== bigrams2
[j]) {
308 } else if (bigram1
&& bigrams2
[j] && bigram1
.toLowerCase() == bigrams2
[j].toLowerCase()) {
315 return (2.0 * intersection
) / (length1
+ length2
);