app.value('pathIndex', {}); app.provider('praxisDocsSearch', function() {
function localSearchFactory(Documentation, $timeout, $q, $state, pathIndex) { 'ngInject'; console.log('Using Local Search Index'); // Create the lunr index var index = lunr(function() { this.ref('path'); this.field('titleWords', {boost: 50}); this.field('members', {boost: 40}); this.field('keywords', {boost: 20}); this.field('parent', {boost: 30}); }); // Delay building the index by loading the data asynchronously var indexReadyPromise = Documentation.versions().then(function(versions) { // Delay building the index for 500ms to allow the page to render return $timeout(function() { $q.all(versions.map(function (version) { return Documentation.items(version).then(function(items) { function extractMembers(thing) { return _.map(_.get(thing, 'type.attributes', {}), function(v, k) { var res = [k]; if (v.description) { res.push(v.description); } if (_.get(v, 'type.attributes')) { res = res.concat(extractMembers(v)); } return res.join(' '); }).join(' '); } function extractActionMembers(action) { var headers = _.keys(_.get(action, 'headers.type.attributes', {})).join(' '), params = extractMembers(_.get(action, 'params', {})), payload = extractMembers(_.get(action, 'payload', {})), responses = _.map(_.get(action, 'responses'), function(v, k) { return k + ' ' + v.description; }).join(' '), urls = _.map(_.get(action, 'urls'), 'path').join(' '); return [headers, params, payload, responses, urls].join(' '); } function extractTypeMembers(type) { return extractMembers({type: type}); } _.each(items.resources, function(resource, id) { var href = $state.href('root.controller', {version: version, controller: id}); index.add({ path: href, titleWords: resource.display_name, members: _.map(resource.actions, 'name').join(' ') + ' ' + _.get(resource.media_type, 'name') + ' ' + _.get(resource, 'traits', []).join(' '), keywords: resource.description + ' ' + version, parent: resource.parent ? items.resources[resource.parent].display_name : '' }); pathIndex[href] = { type: 'resource', name: resource.display_name, id: id, version: version }; _.each(resource.actions, function(action) { var href = $state.href('root.action', {version: version, controller: id, action: action.name}); index.add({ path: $state.href('root.action', {version: version, controller: id, action: action.name}), titleWords: action.name, members: extractActionMembers(action), keywords: action.description + ' ' + version, parent: resource.display_name }); pathIndex[href] = { type: 'action', name: resource.display_name + ' ยป ' + action.name, id: action.name, version: version, resource: id }; }); }); _.each(items.schemas, function(type) { var href = $state.href('root.type', {version: version, type: type.id}); index.add({ path: href, titleWords: type.display_name, members: extractTypeMembers(type), keywords: type.description + ' ' + version }); pathIndex[href] = { type: 'schema', name: type.display_name, id: type.id, version: version }; }); _.each(items.traits, function(trait, id) { var href = $state.href('root.trait', {version: version, trait: id}); index.add({ path: href, titleWords: id, members: extractTypeMembers(trait), keywords: trait.description + ' ' + version }); pathIndex[href] = { type: 'trait', name: id, id: id, version: version }; }); }); })); }, 500); }); // The actual service is a function that takes a query string and // returns a promise to the search results // (In this case we just resolve the promise immediately as it is not // inherently an async process) return function(q) { return indexReadyPromise.then(function() { return index.search(q); }); }; } return { $get:localSearchFactory //window.Worker ? webWorkerSearchFactory : localSearchFactory };
});
app.controller('DocsSearchCtrl', function($scope, $location, praxisDocsSearch, pathIndex, $timeout) {
function clearResults() { $scope.results = []; $scope.showResults = false; $scope.colClassName = null; $scope.hasResults = false; } clearResults(); $scope.focus = false; $scope.search = function(q) { var MIN_SEARCH_LENGTH = 2; if(q.length >= MIN_SEARCH_LENGTH) { praxisDocsSearch(q).then(function(hits) { $scope.hasResults = hits.length > 0; $scope.results = _.map(_.take(hits, 10), function(hit) { var result = pathIndex[hit.ref]; result.path = hit.ref; return result; }); }); } else { clearResults(); } if(!$scope.$$phase) $scope.$apply(); }; $scope.submit = function() { var result; for(var i in $scope.results) { result = $scope.results[i]; if(result) { break; } } if(result) { $location.path(result.path); $scope.hideResults(); } }; $scope.hideResults = function() { clearResults(); $scope.q = ''; }; $scope.goToResult = function(result) { var str = $scope.q; $scope.hideResults(); $timeout(function() { if (window.find) { // Firefox, Google Chrome, Safari // if some content is selected, the start position of the search // will be the end position of the selection window.find(str, false, false, true); } else { if (document.selection && document.selection.createRange) { // Internet Explorer, Opera before version 10.5 var textRange = document.selection.createRange (); if (textRange.findText) { // Internet Explorer // if some content is selected, the start position of the search // will be the position after the start position of the selection if (textRange.text.length > 0) { textRange.collapse(true); textRange.mov("character", 1); } if (textRange.findText(str)) { textRange.select(); } } } } }, 200); }; $scope.$watch('focus && hasResults', function(val) { $scope.showResults = val; });
});
app.directive('searchForm', function() {
return { restrict: 'E', controller: 'DocsSearchCtrl', templateUrl: 'views/search_form.html' };
});