Merge: nitweb: search field
authorJean Privat <jean@pryen.org>
Fri, 3 Jun 2016 17:16:57 +0000 (13:16 -0400)
committerJean Privat <jean@pryen.org>
Fri, 3 Jun 2016 17:16:57 +0000 (13:16 -0400)
* Introduce a search field in the top navbar connected to the `/search` service.
* Added a stub implementation of a search service depending on APIList.

Demo: http://nitweb.moz-code.org/

Pull-Request: #2157
Reviewed-by: Jean Privat <jean@pryen.org>

share/nitweb/index.html
share/nitweb/javascripts/model.js
share/nitweb/javascripts/ui.js
share/nitweb/stylesheets/nitweb.css
src/web/model_api.nit

index f2a54c5..17bcd23 100644 (file)
                                <div class='col-xs-3 navbar-header'>
                                        <a class='navbar-brand' ng-href='/'>Nitdoc</a>
                                </div>
+                               <div class='col-xs-7'>
+                                       <form ng-controller='SearchCtrl as searchCtrl' >
+                                               <div class='form-group has-icon'>
+                                                       <input placeholder='Search...' type='text' class='form-control search-input'
+                                                               ng-model-options='{ debounce: 150 }' ng-model='query'
+                                                               ng-keydown='update($event)' ng-change='search()'>
+                                                       <span class='glyphicon glyphicon-search form-control-icon text-muted'></span>
+                                               </div>
+                                               <div ng-if='results.length > 0' class='search-results'>
+                                                       <div class='card-list'>
+                                                               <entity-card ng-click='reset()' ng-class='{active: activeItem == $index}' mentity='mentity' ng-repeat='mentity in results' />
+                                                       </div>
+                                               </div>
+                                       </form>
+                               </div>
                        </div>
                </nav>
                <div ng-view></div>
index b5e8bbf..7d5051b 100644 (file)
                                        $http.get(apiUrl + '/entity/' + id)
                                                .success(cb)
                                                .error(cbErr);
-                               }
+                               },
 
+                               search: function(q, n, cb, cbErr) {
+                                       $http.get(apiUrl + '/search?q=' + q + '&n=' + n)
+                                               .success(cb)
+                                               .error(cbErr);
+                               }
                        };
                }])
 })();
index aae0234..cd30d06 100644 (file)
        angular
                .module('ui', [ 'model' ])
 
+               .controller('SearchCtrl', ['Model', '$routeParams', '$scope', '$window', function(Model, $routeParams, $scope, $window) {
+                       $scope.query = '';
+
+                       $scope.reset = function() {
+                               $scope.activeItem = 0;
+                               $scope.results = [];
+                       }
+
+                       $scope.update = function(e) {
+                               if(e.keyCode == 38) {
+                                       $scope.selectUp();
+                               } else if(e.keyCode == 40) {
+                                       $scope.selectDown();
+                               } else if(e.keyCode == 27) {
+                                       $scope.selectEscape();
+                               } else if(e.keyCode == 13) {
+                                       $scope.selectEnter();
+                               }
+                       }
+
+                       $scope.selectUp = function() {
+                               if($scope.activeItem > 0) {
+                                       $scope.activeItem -= 1;
+                               }
+                       }
+
+                       $scope.selectDown = function() {
+                               if($scope.activeItem < $scope.results.length - 1) {
+                                       $scope.activeItem += 1;
+                               }
+                       }
+
+                       $scope.selectEnter = function() {
+                               $window.location.href = $scope.results[$scope.activeItem].web_url;
+                               $scope.reset();
+                       }
+
+                       $scope.selectEscape = function() {
+                               $scope.reset();
+                       }
+
+                       $scope.search = function() {
+                               if(!$scope.query) {
+                                       $scope.reset();
+                                       return;
+                               }
+                               Model.search($scope.query, 10,
+                                       function(data) {
+                                               $scope.reset();
+                                               $scope.results = data;
+                                       }, function(err) {
+                                               $scope.reset();
+                                               $scope.error = err;
+                                       });
+                       }
+
+                       $scope.reset();
+               }])
+
                .directive('uiFilters', function() {
                        return {
                                restrict: 'E',
index eca9c48..2d38457 100644 (file)
@@ -145,6 +145,66 @@ entity-list:hover .btn-filter {
     pointer-events: none;
 }
 
+/* search */
+
+.search-input {
+       width: 100%;
+}
+
+.search-results {
+       position: absolute;
+       right: 0;
+}
+
+.search-results .card.active {
+       background: #eee;
+       border-color: #eee;
+}
+
+/* navs */
+
+.nav-tabs li { cursor: pointer; }
+
+.navbar-fixed-top {
+       background-color: #1E9431;
+       box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28);
+}
+
+.navbar-fixed-top .form-control:hover, .navbar-fixed-top .form-control:focus {
+       background: rgba(255, 255, 255, 0.2);
+}
+
+.navbar-fixed-top .form-control {
+       background: rgba(255, 255, 255, 0.1);
+    border: none;
+    color: #fff;
+    box-shadow: none;
+}
+
+.navbar-fixed-top .form-control-icon {
+       color: #fff;
+}
+
+.navbar-fixed-top *::-webkit-input-placeholder {
+    color: #fff;
+}
+.navbar-fixed-top *:-moz-placeholder {
+    /* FF 4-18 */
+    color: #fff;
+}
+.navbar-fixed-top *::-moz-placeholder {
+    /* FF 19+ */
+    color: #fff;
+}
+.navbar-fixed-top *:-ms-input-placeholder {
+    /* IE 10+ */
+    color: #fff;
+}
+
+.navbar-fixed-top .form-group {
+       margin-top: 8px;
+       margin-bottom: 0px;
+}
 /*
  * Code Highlighting
  */
index 85a2360..9bbe5ad 100644 (file)
@@ -77,26 +77,6 @@ class APIRouter
        end
 end
 
-# Search mentities from a query string.
-#
-# Example: `GET /search?q=Arr`
-class APISearch
-       super APIHandler
-
-       redef fun get(req, res) do
-               var q = req.string_arg("q")
-               if q == null then
-                       res.error 400
-                       return
-               end
-               var arr = new JsonArray
-               for mentity in view.mentities do
-                       if mentity.name.has_prefix(q) then arr.add mentity
-               end
-               res.json arr
-       end
-end
-
 # List all mentities.
 #
 # MEntities can be filtered on their kind using the `k` parameter.
@@ -144,9 +124,24 @@ class APIList
        redef fun get(req, res) do
                var mentities = list_mentities(req)
                mentities = limit_mentities(req, mentities)
-               var arr = new JsonArray
-               for mentity in mentities do arr.add mentity
-               res.json arr
+               res.json new JsonArray.from(mentities)
+       end
+end
+
+# Search mentities from a query string.
+#
+# Example: `GET /search?q=Arr`
+class APISearch
+       super APIList
+
+       redef fun list_mentities(req) do
+               var q = req.string_arg("q")
+               var mentities = new Array[MEntity]
+               if q == null then return mentities
+               for mentity in view.mentities do
+                       if mentity.name.has_prefix(q) then mentities.add mentity
+               end
+               return mentities
        end
 end
 
@@ -167,9 +162,7 @@ class APIRandom
                var mentities = list_mentities(req)
                mentities = limit_mentities(req, mentities)
                mentities = randomize_mentities(req, mentities)
-               var arr = new JsonArray
-               for mentity in mentities do arr.add mentity
-               res.json arr
+               res.json new JsonArray.from(mentities)
        end
 end