nitdoc: Add quick search results preview.
authorAlexandre Terrasa <alexandre@moz-concept.com>
Tue, 17 Apr 2012 16:17:37 +0000 (12:17 -0400)
committerAlexandre Terrasa <alexandre@moz-concept.com>
Tue, 17 Apr 2012 16:17:37 +0000 (12:17 -0400)
Signed-off-by: Alexandre Terrasa <alexandre@moz-concept.com>

share/nitdoc/scripts/js-facilities.js
share/nitdoc/styles/main.css
src/nitdoc.nit
tests/sav/nitdoc_args1.sav

index 669ad08..d67ef9c 100644 (file)
@@ -5,6 +5,16 @@ $.expr[':'].icontains = function(obj, index, meta, stack){
        return (obj.textContent.replace(/\[[0-9]+\]/g, "") || obj.innerText.replace(/\[[0-9]+\]/g, "") || jQuery(obj).text().replace(/\[[0-9]+\]/g, "") || '').toLowerCase().indexOf(meta[3].toLowerCase()) >= 0;\r
 };\r
 \r
+/*\r
+ *     Quick Search global vars\r
+ */\r
\r
+// Current search results preview table\r
+var currentTable = null;\r
+\r
+//Hightlighted index in search result preview table\r
+var currentIndex = -1;\r
+\r
 \r
 /*\r
 * Add folding and filtering facilities to class description page.\r
@@ -37,48 +47,166 @@ $(document).ready(function() {
                        $(this).nextAll().toggle();\r
        })
        
-       // Instert search field
-        $("nav.main ul")
-        .append(
-                       $(document.createElement("li"))
-                       .append(
-                               $(document.createElement("form"))
-                               .append(
-                                       $(document.createElement("input"))\r
-                                       .attr({
-                                               id: "search",\r
-                                               type:   "text",\r
-                                               value: "quick search..."\r
-                                       })\r
-                                       .addClass("notUsed")\r
-                                       .keyup(function() {\r
-                                               $(this).parent().parent().find("ul li:not(:icontains('" + $(this).val() + "'))").addClass("hide");\r
-                                               $(this).parent().parent().find("ul li:icontains('" + $(this).val() + "')").removeClass("hide");\r
-                                       })\r
-                                       .focusout(function() {\r
-                                               if($(this).val() == "") {\r
-                                                       $(this).addClass("notUsed");\r
-                                                       $(this).val("quick search...");\r
-                                               }\r
-                                       })\r
-                                       .focusin(function() {\r
-                                               if($(this).val() == "quick search...") {\r
-                                                       $(this).removeClass("notUsed");\r
-                                                       $(this).val("");\r
-                                               }\r
-                                       })
-                               )
-                               .submit( function() {
-                                       if($("#search").val().length == 0)
-                                               return false
-                                       
-                                       window.location = "full-index.html#q=" + $("#search").val();
-                                       if(window.location.href.indexOf("full-index.html") > -1) {
-                                               location.reload();
-                                       }                               
-                                       return false;
-                               })
-                       )
+       // Insert search field\r
+       $("nav.main ul")\r
+       .append(\r
+               $(document.createElement("li"))\r
+               .append(\r
+                       $(document.createElement("form"))\r
+                       .append(\r
+                               $(document.createElement("input"))\r
+                               .attr({\r
+                                       id: "search",\r
+                                       type:   "text",\r
+                                       autocomplete: "off",\r
+                                       value: "quick search..."\r
+                               })\r
+                               .addClass("notUsed")\r
+\r
+                               // Key management\r
+                               .keyup(function(e) {\r
+                                       switch(e.keyCode) {\r
+\r
+                                               // Select previous result on "Up"\r
+                                               case 38:\r
+                                                       // If already on first result, focus search input\r
+                                                       if(currentIndex == 0) {\r
+                                                               $("#search").val($(currentTable.find("tr")[currentIndex]).data("searchDetails").name);\r
+                                                               $("#search").focus();\r
+                                                       // Else select previous result\r
+                                                       } else if(currentIndex > 0) {\r
+                                                               $(currentTable.find("tr")[currentIndex]).removeClass("activeSearchResult");\r
+                                                               currentIndex--;\r
+                                                               $(currentTable.find("tr")[currentIndex]).addClass("activeSearchResult");\r
+                                                               $("#search").val($(currentTable.find("tr")[currentIndex]).data("searchDetails").name);\r
+                                                               $("#search").focus();\r
+                                                       }\r
+                                               break;\r
+\r
+                                               // Select next result on "Down"\r
+                                               case 40:\r
+                                                       if(currentIndex < currentTable.find("tr").length - 1) {\r
+                                                               $(currentTable.find("tr")[currentIndex]).removeClass("activeSearchResult");\r
+                                                               currentIndex++;\r
+                                                               $(currentTable.find("tr")[currentIndex]).addClass("activeSearchResult");\r
+                                                               $("#search").val($(currentTable.find("tr")[currentIndex]).data("searchDetails").name);\r
+                                                               $("#search").focus();\r
+                                                       }\r
+                                               break;\r
+                                               // Go to url on "Enter"\r
+                                               case 13:\r
+                                                       if(currentIndex > -1) {\r
+                                                               window.location = $(currentTable.find("tr")[currentIndex]).data("searchDetails").url;\r
+                                                               return false;\r
+                                                       }\r
+                                                       if($("#search").val().length == 0)\r
+                                                               return false\r
+                               \r
+                                                       window.location = "full-index.html#q=" + $("#search").val();\r
+                                                       if(window.location.href.indexOf("full-index.html") > -1) {\r
+                                                               location.reload();\r
+                                                       }                               \r
+                                                       return false;\r
+                                               break;\r
+\r
+                                               // Hide results preview on "Escape"\r
+                                               case 27:\r
+                                                       $(this).blur();\r
+                                                       if(currentTable != null) {\r
+                                                               currentTable.remove();\r
+                                                               currentTable = null;\r
+                                                       }\r
+                                               break;\r
+\r
+                                               default:\r
+                                                       if($("#search").val().length == 0) {\r
+                                                               return false;\r
+                                                       }\r
+                                               \r
+                                                       // Remove previous table\r
+                                                       if(currentTable != null) {\r
+                                                               currentTable.remove();\r
+                                                       }\r
+\r
+                                                       // Build results table\r
+                                                       currentIndex = -1;\r
+                                                       currentTable = $(document.createElement("table"));\r
+\r
+                                                       // Escape regexp related characters in query\r
+                                                       var query = $("#search").val();\r
+                                                       query = query.replace(/\[/gi, "\\[");\r
+                                                       query = query.replace(/\|/gi, "\\|");\r
+                                                       query = query.replace(/\*/gi, "\\*");\r
+                                                       query = query.replace(/\+/gi, "\\+");\r
+                                                       query = query.replace(/\\/gi, "\\\\");\r
+                                                       query = query.replace(/\?/gi, "\\?");\r
+                                                       query = query.replace(/\(/gi, "\\(");\r
+                                                       query = query.replace(/\)/gi, "\\)");\r
+\r
+                                                       var index = 0;\r
+                                                       var regexp = new RegExp("^" + query, "i");\r
+                                                       for(var entry in entries) {\r
+                                                               if(index > 10) {\r
+                                                                       break;\r
+                                                               }\r
+                                                               var result = entry.match(regexp);\r
+                                                               if(result != null && result.toString().toUpperCase() == $("#search").val().toUpperCase()) {\r
+                                                                       for(var i = 0; i < entries[entry].length; i++) {\r
+                                                                               if(index > 10) {\r
+                                                                                       break;\r
+                                                                               }\r
+                                                                               currentTable.append(\r
+                                                                                       $(document.createElement("tr"))\r
+                                                                                       .data("searchDetails", {name: entry, url: entries[entry][i]["url"]})\r
+                                                                                       .data("index", index)\r
+                                                                                       .append($(document.createElement("td")).html(entry))\r
+                                                                                       .append(\r
+                                                                                               $(document.createElement("td"))\r
+                                                                                                       .addClass("entryInfo")\r
+                                                                                                       .html(entries[entry][i]["txt"] + "&nbsp;&raquo;"))\r
+                                                                                       .mouseover( function() {\r
+                                                                                               $(currentTable.find("tr")[currentIndex]).removeClass("activeSearchResult");\r
+                                                                                               $(this).addClass("activeSearchResult");\r
+                                                                                               currentIndex = $(this).data("index");\r
+                                                                                       })\r
+                                                                                       .mouseout( function() {\r
+                                                                                               $(this).removeClass("activeSearchResult");\r
+                                                                                        })\r
+                                                                                       .click( function() {\r
+                                                                                               window.location = $(this).data("searchDetails")["url"];\r
+                                                                                       })\r
+                                                                               );\r
+                                                                               index++;\r
+                                                                       }\r
+                                                               }\r
+                                                       }\r
+                                                       \r
+                                                       // Initialize table properties\r
+                                                       currentTable.attr("id", "searchTable");\r
+                                                       currentTable.css("position", "absolute");\r
+                                                       currentTable.width($("#search").outerWidth());\r
+                                                       $("header").append(currentTable);\r
+                                                       currentTable.offset({left: $("#search").offset().left + ($("#search").outerWidth() - currentTable.outerWidth()), top: $("#search").offset().top + $("#search").outerHeight()});\r
+                                               break;\r
+                                       }\r
+                               })\r
+                               .focusout(function() {\r
+                                       if($(this).val() == "") {\r
+                                               $(this).addClass("notUsed");\r
+                                               $(this).val("quick search...");\r
+                                       }\r
+                               })\r
+                               .focusin(function() {\r
+                                       if($(this).val() == "quick search...") {\r
+                                               $(this).removeClass("notUsed");\r
+                                               $(this).val("");\r
+                                       }\r
+                               })\r
+                       )\r
+                       .submit( function() {\r
+                               return false;\r
+                       })\r
+               )\r
         );\r
        \r
        /*\r
index 253634e..07214ec 100644 (file)
@@ -256,7 +256,6 @@ article .info .code {
 input[type=text] {\r
        width: 150px;\r
        border: 1px solid #CCC;\r
-       border-radius: 5px;\r
        margin-right: 5px;
        padding: 1px 2px;\r
 }
@@ -321,4 +320,36 @@ nav h3 a.fold {
 .init.private { background-image: url('../resources/icons/const_private.png')}\r
 .fun.public { background-image: url('../resources/icons/meth_public.png')}\r
 .fun.protected { background-image: url('../resources/icons/meth_protected.png')}\r
-.fun.private { background-image: url('../resources/icons/meth_private.png')}
\ No newline at end of file
+.fun.private { background-image: url('../resources/icons/meth_private.png')}\r
+\r
+/* Quick Search */\r
+\r
+#search {\r
+       width: 300px;\r
+}\r
+\r
+#searchTable {\r
+       background-color: #FFFFFF;\r
+       border: 1px solid #E0E0E0;\r
+       border-spacing: 0px;\r
+}\r
+\r
+#searchTable .activeSearchResult {\r
+       cursor: pointer;\r
+       background: #EEE;\r
+}\r
+\r
+#searchTable td {\r
+       white-space: nowrap;\r
+       overflow: hidden;\r
+       line-height: 22px;\r
+       padding: 2px;\r
+       width: 25%;\r
+}\r
+\r
+#searchTable td.entryInfo {\r
+       color: #0D8921;\r
+       font-size: small;\r
+       width: 75%;\r
+       text-align: right;\r
+}
\ No newline at end of file
index 2765264..03adf6b 100644 (file)
@@ -157,7 +157,8 @@ class DocContext
                end
 
                var head = "<meta charset=\"utf-8\">" +
-                       "<script type=\"text/javascript\" src=\"scripts/jquery-1.7.1.min.js\"></script>\n" + 
+                       "<script type=\"text/javascript\" src=\"scripts/jquery-1.7.1.min.js\"></script>\n" +
+                       "<script type=\"text/javascript\" src=\"quicksearch-list.js\"></script>\n" +
                        "<script type=\"text/javascript\" src=\"scripts/js-facilities.js\"></script>\n" +
                        "<link rel=\"stylesheet\" href=\"styles/main.css\" type=\"text/css\"  media=\"screen\" />"
 
@@ -259,6 +260,11 @@ class DocContext
                add("<footer>{footer_text}</footer>")
                add("</body></html>\n")
                write_to("{dir}/full-index.html")
+
+               self.filename = "quicksearch-list"
+               clear
+               mainmod.file_quicksearch_list_doc(self)
+               write_to("{dir}/quicksearch-list.js")
        end
 
 
@@ -437,6 +443,13 @@ class MMEntity
        # The doc node from the AST
        # Return null is none
        fun doc: nullable ADoc do return null
+
+       # Return a jason entry for quicksearch list JSON Object
+       fun json_entry(dctx: DocContext): String is abstract
+
+       # Return the qualified name as string
+       fun qualified_name: String is abstract
+               
 end
 
 redef class MMModule
@@ -445,6 +458,19 @@ redef class MMModule
                return "<a href=\"{html_name}.html\" title=\"{short_doc}\">{self}</a>"
        end
 
+       redef fun json_entry(dctx) do
+               return "\{txt:\"{self.qualified_name}\",url:\"{html_name}.html\"\},"
+       end
+
+       redef fun qualified_name do
+               var buffer = new Buffer
+               for m in mnhe.smallers do
+                       buffer.append("{m.html_name}::")
+               end
+               buffer.append("{self.name}")
+               return buffer.to_s
+       end
+
        fun require_doc(dctx: DocContext): Bool
        do
                if dctx.public_only and not is_toplevel then return false
@@ -787,8 +813,66 @@ redef class MMModule
                dctx.stage("</ul></article>\n")
                dctx.close_stage
        end
+
+       # Fill the quicksearch list JSON object
+       fun file_quicksearch_list_doc(dctx: DocContext)
+       do
+               var entities = new HashMap[String, Array[MMEntity]]
+               var props = new HashMap[MMGlobalProperty, Array[MMLocalProperty]]
+               for m in mhe.greaters_and_self do
+                       if not m.require_doc(dctx) then continue
+                       var a = new Array[MMEntity]
+                       a.add(m)
+                       entities[m.html_name] = a
+               end
+               for g in global_classes do
+                       var lc = self[g]
+                       if not lc.require_doc(dctx) then continue
+                       var a = new Array[MMEntity]
+                       a.add(lc)
+                       entities[lc.html_name] = a
+                       for gp in lc.global_properties do
+                               var lp = lc[gp]
+                               if not lp.require_doc(dctx) then continue
+                               if props.has_key(lp.global) then
+                                       if not props[lp.global].has(lp) then
+                                               props[lp.global].add(lp)
+                                       end
+                               else
+                                       props[lp.global] = [lp]
+                               end
+                       end
+               end
+
+               for k, v in props do
+                       entities[k.short_name] = v
+               end
+
+               var keys = entities.keys.to_a
+               var sorter = new AlphaSorter[String]
+               sorter.sort(keys)
+               
+               dctx.open_stage
+               dctx.stage("var entries = \{")
+               for key in keys do
+                       dctx.add("\"{key}\": [")
+                       for entity in entities[key] do
+                               dctx.add(entity.json_entry(dctx))
+                       end
+                       dctx.add("],")
+               end
+               dctx.stage("\};")
+               dctx.close_stage
+       end
 end
 
+redef class MMGlobalProperty
+       # Return the short name of the property
+       fun short_name: String do
+               return self.intro.html_name
+       end
+end 
+
 redef class MMLocalProperty
        super MMEntity
        # Anchor of the property description in the module html file
@@ -797,6 +881,14 @@ redef class MMLocalProperty
                return "PROP_{local_class}_{cmangle(name)}"
        end
 
+       redef fun json_entry(dctx) do
+               return "\{txt:\"{qualified_name}\",url:\"{local_class.html_name}.html#{html_anchor}\"\},"
+       end
+
+       redef fun qualified_name do
+               return "{intro_module.qualified_name}::{local_class.html_name}::{html_name}"
+       end
+
        fun html_open_link(dctx: DocContext): String
        do
                if not require_doc(dctx) then print "not required {self}"
@@ -1107,6 +1199,14 @@ redef class MMLocalClass
                return "<a href=\"{html_name}.html\" title=\"{short_doc}\">{self}</a>"
        end
 
+       redef fun json_entry(dctx) do
+               return "\{txt:\"{qualified_name}\",url:\"{html_name}.html\"\},"
+       end
+
+       redef fun qualified_name do
+               return "{intro_module.qualified_name}::{html_name}"
+       end
+
        redef fun short_doc do return global.intro.short_doc
 
        redef fun doc do return global.intro.doc
index 4a7eeb6..910352f 100644 (file)
@@ -557,6 +557,7 @@ opts.html
 opts.map
 opts.png
 opts.s.dot
+quicksearch-list.js
 range.dot
 range.html
 range.map