From 6e9fce6d85176f1bc85cb387bbf9d9efc4828790 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 21 Sep 2014 21:12:21 +0100 Subject: [PATCH 01/10] chore(docs): remove unused code --- docs/app/src/docs.js | 42 +------------------------------ docs/app/src/navigationService.js | 24 ------------------ 2 files changed, 1 insertion(+), 65 deletions(-) delete mode 100644 docs/app/src/navigationService.js diff --git a/docs/app/src/docs.js b/docs/app/src/docs.js index 84ccef43cc64..e0f6b5509082 100644 --- a/docs/app/src/docs.js +++ b/docs/app/src/docs.js @@ -11,20 +11,7 @@ angular.module('DocsController', []) $scope.docsVersion = NG_VERSION.isSnapshot ? 'snapshot' : NG_VERSION.version; - $scope.fold = function(url) { - if(url) { - $scope.docs_fold = '/notes/' + url; - if(/\/build/.test($window.location.href)) { - $scope.docs_fold = '/build/docs' + $scope.docs_fold; - } - window.scrollTo(0,0); - } - else { - $scope.docs_fold = null; - } - }; - var OFFLINE_COOKIE_NAME = 'ng-offline', - INDEX_PATH = /^(\/|\/index[^\.]*.html)$/; + var INDEX_PATH = /^(\/|\/index[^\.]*.html)$/; /********************************** @@ -43,20 +30,6 @@ angular.module('DocsController', []) $window._gaq.push(['_trackPageview', pagePath]); }; - /** stores a cookie that is used by apache to decide which manifest ot send */ - $scope.enableOffline = function() { - //The cookie will be good for one year! - var date = new Date(); - date.setTime(date.getTime()+(365*24*60*60*1000)); - var expires = "; expires="+date.toGMTString(); - var value = angular.version.full; - document.cookie = OFFLINE_COOKIE_NAME + "="+value+expires+"; path=" + $location.path; - - //force the page to reload so server can serve new manifest file - window.location.reload(true); - }; - - /********************************** Watches @@ -107,24 +80,11 @@ angular.module('DocsController', []) $scope.versionNumber = angular.version.full; $scope.version = angular.version.full + " " + angular.version.codeName; - $scope.subpage = false; - $scope.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full); - $scope.futurePartialTitle = null; $scope.loading = 0; - $scope.$cookies = $cookies; - $cookies.platformPreference = $cookies.platformPreference || 'gitUnix'; if (!$location.path() || INDEX_PATH.test($location.path())) { $location.path('/api').replace(); } - // bind escape to hash reset callback - angular.element(window).on('keydown', function(e) { - if (e.keyCode === 27) { - $scope.$apply(function() { - $scope.subpage = false; - }); - } - }); }]); diff --git a/docs/app/src/navigationService.js b/docs/app/src/navigationService.js deleted file mode 100644 index fbee1801961c..000000000000 --- a/docs/app/src/navigationService.js +++ /dev/null @@ -1,24 +0,0 @@ -angular.module('docsApp.navigationService', []) - -.factory('navigationService', function($window) { - var service = { - currentPage: null, - currentVersion: null, - changePage: function(newPage) { - - }, - changeVersion: function(newVersion) { - - //TODO ========= - // var currentPagePath = ''; - - // // preserve URL path when switching between doc versions - // if (angular.isObject($rootScope.currentPage) && $rootScope.currentPage.section && $rootScope.currentPage.id) { - // currentPagePath = '/' + $rootScope.currentPage.section + '/' + $rootScope.currentPage.id; - // } - - // $window.location = version.url + currentPagePath; - - } - }; -}); From e58e9d89bd36f6aa071e36c8fc940e7b0de43841 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 21 Sep 2014 21:15:12 +0100 Subject: [PATCH 02/10] chore(docs): separate page and nav data into separate files --- docs/app/src/app.js | 1 + docs/config/processors/pages-data.js | 17 ++++++++++------- docs/config/services/deployments/debug.js | 1 + docs/config/services/deployments/default.js | 1 + docs/config/services/deployments/jquery.js | 1 + docs/config/services/deployments/production.js | 1 + docs/config/templates/nav-data.template.js | 3 +++ docs/config/templates/pages-data.template.js | 3 +-- 8 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 docs/config/templates/nav-data.template.js diff --git a/docs/app/src/app.js b/docs/app/src/app.js index 3015be958668..ba2fcdaea6ba 100644 --- a/docs/app/src/app.js +++ b/docs/app/src/app.js @@ -6,6 +6,7 @@ angular.module('docsApp', [ 'DocsController', 'versionsData', 'pagesData', + 'navData', 'directives', 'errors', 'examples', diff --git a/docs/config/processors/pages-data.js b/docs/config/processors/pages-data.js index 7656db5951bd..8bc04fac1b87 100644 --- a/docs/config/processors/pages-data.js +++ b/docs/config/processors/pages-data.js @@ -209,17 +209,20 @@ module.exports = function generatePagesDataProcessor(log) { .indexBy('path') .value(); - var docData = { + docs.push({ docType: 'pages-data', id: 'pages-data', template: 'pages-data.template.js', outputPath: 'js/pages-data.js', - - areas: areas, pages: pages - }; - - docs.push(docData); + }); + docs.push({ + docType: 'nav-data', + id: 'nav-data', + template: 'nav-data.template.js', + outputPath: 'js/nav-data.js', + areas: areas + }); } - } + }; }; diff --git a/docs/config/services/deployments/debug.js b/docs/config/services/deployments/debug.js index f6be3dafe83b..cade0bc8c644 100644 --- a/docs/config/services/deployments/debug.js +++ b/docs/config/services/deployments/debug.js @@ -26,6 +26,7 @@ module.exports = function debugDeployment(getVersion) { 'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js', 'js/versions-data.js', 'js/pages-data.js', + 'js/nav-data.js', 'js/docs.js' ], stylesheets: [ diff --git a/docs/config/services/deployments/default.js b/docs/config/services/deployments/default.js index c12ff4f3b7b0..ad732592c111 100644 --- a/docs/config/services/deployments/default.js +++ b/docs/config/services/deployments/default.js @@ -26,6 +26,7 @@ module.exports = function defaultDeployment(getVersion) { 'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js', 'js/versions-data.js', 'js/pages-data.js', + 'js/nav-data.js', 'js/docs.js' ], stylesheets: [ diff --git a/docs/config/services/deployments/jquery.js b/docs/config/services/deployments/jquery.js index 55340b1e7f80..fe3feea2dd8e 100644 --- a/docs/config/services/deployments/jquery.js +++ b/docs/config/services/deployments/jquery.js @@ -30,6 +30,7 @@ module.exports = function jqueryDeployment(getVersion) { 'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js', 'js/versions-data.js', 'js/pages-data.js', + 'js/nav-data.js', 'js/docs.js' ], stylesheets: [ diff --git a/docs/config/services/deployments/production.js b/docs/config/services/deployments/production.js index 13d18a160a2a..8ab1b11d8ac8 100644 --- a/docs/config/services/deployments/production.js +++ b/docs/config/services/deployments/production.js @@ -29,6 +29,7 @@ module.exports = function productionDeployment(getVersion) { 'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js', 'js/versions-data.js', 'js/pages-data.js', + 'js/nav-data.js', 'js/docs.js' ], stylesheets: [ diff --git a/docs/config/templates/nav-data.template.js b/docs/config/templates/nav-data.template.js new file mode 100644 index 000000000000..13910b48d476 --- /dev/null +++ b/docs/config/templates/nav-data.template.js @@ -0,0 +1,3 @@ +// Meta data used by the AngularJS docs app +angular.module('navData', []) + .value('NG_NAVIGATION', {$ doc.areas | json $}); diff --git a/docs/config/templates/pages-data.template.js b/docs/config/templates/pages-data.template.js index 157f786ef9bf..1be7df506bd4 100644 --- a/docs/config/templates/pages-data.template.js +++ b/docs/config/templates/pages-data.template.js @@ -1,4 +1,3 @@ // Meta data used by the AngularJS docs app angular.module('pagesData', []) - .value('NG_PAGES', {$ doc.pages | json $}) - .value('NG_NAVIGATION', {$ doc.areas | json $}); + .value('NG_PAGES', {$ doc.pages | json $}); From de15709a4c3469194dc7d29b1ba362a8782afb03 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 21 Sep 2014 21:31:41 +0100 Subject: [PATCH 03/10] chore(docs): delay building search index by 1 sec --- docs/app/src/search.js | 44 +++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/docs/app/src/search.js b/docs/app/src/search.js index 7e069981263e..a8c71522d171 100644 --- a/docs/app/src/search.js +++ b/docs/app/src/search.js @@ -50,26 +50,11 @@ angular.module('search', []) }]) .factory('lunrSearch', function() { - return function(properties) { - if (window.RUNNING_IN_NG_TEST_RUNNER) return null; - - var engine = lunr(properties); - return { - store : function(values) { - engine.add(values); - }, - search : function(q) { - return engine.search(q); - } - }; - }; + return lunr; }) -.factory('docsSearch', ['$rootScope','lunrSearch', 'NG_PAGES', - function($rootScope, lunrSearch, NG_PAGES) { - if (window.RUNNING_IN_NG_TEST_RUNNER) { - return null; - } +.factory('docsSearch', ['$rootScope','lunrSearch', 'NG_PAGES', '$timeout', + function($rootScope, lunrSearch, NG_PAGES, $timeout) { var index = lunrSearch(function() { this.ref('id'); @@ -78,16 +63,19 @@ angular.module('search', []) this.field('keywords', { boost : 20 }); }); - angular.forEach(NG_PAGES, function(page, key) { - if(page.searchTerms) { - index.store({ - id : key, - title : page.searchTerms.titleWords, - keywords : page.searchTerms.keywords, - members : page.searchTerms.members - }); - }; - }); + // Delay building the index for one second to allow the page to render + $timeout(function() { + angular.forEach(NG_PAGES, function(page, key) { + if(page.searchTerms) { + index.add({ + id : key, + title : page.searchTerms.titleWords, + keywords : page.searchTerms.keywords, + members : page.searchTerms.members + }); + }; + }); + }, 1000); return function(q) { var results = { From 3dd23ccd74ee7bd454590a7f7ee346f020615ed8 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Mon, 22 Sep 2014 11:34:03 +0100 Subject: [PATCH 04/10] chore(docs): load pages data asynchronously This is in preparation for moving the work to a WebWorker but will also facilitate, in the future, removing the `pagesData` angular module altogether. --- docs/app/src/search.js | 57 ++++++++++---------- docs/config/processors/pages-data.js | 9 ++++ docs/config/templates/json-doc.template.json | 1 + 3 files changed, 38 insertions(+), 29 deletions(-) create mode 100644 docs/config/templates/json-doc.template.json diff --git a/docs/app/src/search.js b/docs/app/src/search.js index a8c71522d171..f460ca4679e5 100644 --- a/docs/app/src/search.js +++ b/docs/app/src/search.js @@ -49,49 +49,48 @@ angular.module('search', []) $scope.results = docsSearch($location.path().split(/[\/\.:]/).pop()); }]) -.factory('lunrSearch', function() { - return lunr; -}) -.factory('docsSearch', ['$rootScope','lunrSearch', 'NG_PAGES', '$timeout', - function($rootScope, lunrSearch, NG_PAGES, $timeout) { +.factory('docsSearch', ['$http', '$timeout', function($http, $timeout) { - var index = lunrSearch(function() { + var index = lunr(function() { this.ref('id'); this.field('title', {boost: 50}); this.field('members', { boost: 40}); this.field('keywords', { boost : 20 }); }); - // Delay building the index for one second to allow the page to render - $timeout(function() { - angular.forEach(NG_PAGES, function(page, key) { - if(page.searchTerms) { - index.add({ - id : key, - title : page.searchTerms.titleWords, - keywords : page.searchTerms.keywords, - members : page.searchTerms.members - }); - }; - }); - }, 1000); + var pagesData = {}; + + console.time('getting pages data'); + // Delay building the index by loading the data asynchronously + $http.get('js/pages-data.json').then(function(response) { + console.timeEnd('getting pages data'); + pagesData = response.data; + console.time('building index'); + // Delay building the index for 500ms to allow the page to render + $timeout(function() { + angular.forEach(pagesData, function(page, key) { + if(page.searchTerms) { + index.add({ + id : key, + title : page.searchTerms.titleWords, + keywords : page.searchTerms.keywords, + members : page.searchTerms.members + }); + }; + }); + console.timeEnd('building index'); + }, 500); + }); return function(q) { - var results = { - api : [], - tutorial : [], - guide : [], - error : [], - misc : [] - }; + var results = {}; angular.forEach(index.search(q), function(result) { - var key = result.ref; - var item = NG_PAGES[key]; + var item = pagesData[result.ref]; var area = item.area; - item.path = key; var limit = area == 'api' ? 40 : 14; + results[area] = results[area] || []; if(results[area].length < limit) { results[area].push(item); } diff --git a/docs/config/processors/pages-data.js b/docs/config/processors/pages-data.js index 8bc04fac1b87..069fef3b0928 100644 --- a/docs/config/processors/pages-data.js +++ b/docs/config/processors/pages-data.js @@ -216,6 +216,15 @@ module.exports = function generatePagesDataProcessor(log) { outputPath: 'js/pages-data.js', pages: pages }); + + docs.push({ + docType: 'json-doc', + id: 'pages-data-json', + template: 'json-doc.template.json', + outputPath: 'js/pages-data.json', + data: pages + }); + docs.push({ docType: 'nav-data', id: 'nav-data', diff --git a/docs/config/templates/json-doc.template.json b/docs/config/templates/json-doc.template.json new file mode 100644 index 000000000000..3e7f09635414 --- /dev/null +++ b/docs/config/templates/json-doc.template.json @@ -0,0 +1 @@ +{$ doc.data | json $} \ No newline at end of file From da6d066f6562b88842438e5d00c9a197b94df8cb Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Mon, 22 Sep 2014 13:22:00 +0100 Subject: [PATCH 05/10] chore(docs): use a WebWorker for the search index, if available This commit refactors how the search index is built. The docsSearch service is now defined by a provider, which returns a different implementation of the service depending upon whether the current browser supports WebWorkers or now. * **WebWorker supported**: The index is then built and stored in a new worker. The service posts and receives messages to and from this worker to make queries on the search index. * **WebWorker no supported**: The index is built locally but with a 500ms delay so that the initial page can render before the browser is blocked as the index is built. --- docs/app/assets/js/search.js | 54 ++++++++++++ docs/app/src/search.js | 161 +++++++++++++++++++++++------------ 2 files changed, 159 insertions(+), 56 deletions(-) create mode 100644 docs/app/assets/js/search.js diff --git a/docs/app/assets/js/search.js b/docs/app/assets/js/search.js new file mode 100644 index 000000000000..69f5eb046af0 --- /dev/null +++ b/docs/app/assets/js/search.js @@ -0,0 +1,54 @@ +"use strict"; +/* jshint browser: true */ +/* global importScripts, onmessage: true, postMessage, lunr */ + +// Load up the lunr library +importScripts('../components/lunr.js-0.4.2/lunr.min.js'); + +// 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 }); +}); + +// Retrieve the pagesData which contains the information about each +// page to be indexed +var pagesData = {}; +var pagesDataRequest = new XMLHttpRequest(); +pagesDataRequest.onload = function() { + + // Store the pages data to be used in mapping query + // results back to pages + pagesData = JSON.parse(this.responseText); + + // Add search terms from each page to the search index + for(var path in pagesData) { + var page = pagesData[path]; + if(page.searchTerms) { + index.add({ + path : path, // the path is the unique key for the page + titleWords : page.searchTerms.titleWords, + keywords : page.searchTerms.keywords, + members : page.searchTerms.members + }); + } + } +}; +pagesDataRequest.open('GET', 'pages-data.json'); +pagesDataRequest.send(); + +// The worker receives a message everytime the web app +// wants to query the index +onmessage = function(oEvent) { + var q = oEvent.data.q; + var hits = index.search(q); + var results = []; + hits.forEach(function(hit) { + results.push(pagesData[hit.ref]); + }); + // The results of the query are sent back to the web app + // via a new message + postMessage(results); +}; \ No newline at end of file diff --git a/docs/app/src/search.js b/docs/app/src/search.js index f460ca4679e5..7352000260b0 100644 --- a/docs/app/src/search.js +++ b/docs/app/src/search.js @@ -10,22 +10,35 @@ angular.module('search', []) $scope.search = function(q) { var MIN_SEARCH_LENGTH = 2; if(q.length >= MIN_SEARCH_LENGTH) { - var results = docsSearch(q); - var totalAreas = 0; - for(var i in results) { - ++totalAreas; - } - if(totalAreas > 0) { - $scope.colClassName = 'cols-' + totalAreas; - } - $scope.hasResults = totalAreas > 0; - $scope.results = results; + docsSearch(q).then(function(hits) { + var results = {}; + angular.forEach(hits, function(hit) { + var area = hit.area; + + var limit = (area == 'api') ? 40 : 14; + results[area] = results[area] || []; + if(results[area].length < limit) { + results[area].push(hit); + } + }); + + var totalAreas = 0; + for(var i in results) { + ++totalAreas; + } + if(totalAreas > 0) { + $scope.colClassName = 'cols-' + totalAreas; + } + $scope.hasResults = totalAreas > 0; + $scope.results = results; + }); } else { clearResults(); } if(!$scope.$$phase) $scope.$apply(); }; + $scope.submit = function() { var result; for(var i in $scope.results) { @@ -39,6 +52,7 @@ angular.module('search', []) $scope.hideResults(); } }; + $scope.hideResults = function() { clearResults(); $scope.q = ''; @@ -49,55 +63,90 @@ angular.module('search', []) $scope.results = docsSearch($location.path().split(/[\/\.:]/).pop()); }]) +.provider('docsSearch', function() { -.factory('docsSearch', ['$http', '$timeout', function($http, $timeout) { - - var index = lunr(function() { - this.ref('id'); - this.field('title', {boost: 50}); - this.field('members', { boost: 40}); - this.field('keywords', { boost : 20 }); - }); - - var pagesData = {}; - - console.time('getting pages data'); - // Delay building the index by loading the data asynchronously - $http.get('js/pages-data.json').then(function(response) { - console.timeEnd('getting pages data'); - pagesData = response.data; - console.time('building index'); - // Delay building the index for 500ms to allow the page to render - $timeout(function() { - angular.forEach(pagesData, function(page, key) { - if(page.searchTerms) { - index.add({ - id : key, - title : page.searchTerms.titleWords, - keywords : page.searchTerms.keywords, - members : page.searchTerms.members - }); - }; - }); - console.timeEnd('building index'); - }, 500); - }); - - return function(q) { - var results = {}; - angular.forEach(index.search(q), function(result) { - var item = pagesData[result.ref]; - var area = item.area; - - var limit = area == 'api' ? 40 : 14; - results[area] = results[area] || []; - if(results[area].length < limit) { - results[area].push(item); - } + // This version of the service builds the index in the current thread, + // which blocks rendering and other browser activities. + // It should only be used where the browser does not support WebWorkers + function localSearchFactory($http, $timeout, $q) { + + console.log('Using Local Search Index'); + + // Create the lunr index + var index = lunr(function() { + this.ref('id'); + this.field('title', {boost: 50}); + this.field('members', { boost: 40}); + this.field('keywords', { boost : 20 }); + }); + + var pagesData = {}; + + // Delay building the index by loading the data asynchronously + $http.get('js/pages-data.json').then(function(response) { + pagesData = response.data; + // Delay building the index for 500ms to allow the page to render + $timeout(function() { + // load the page data into the index + angular.forEach(pagesData, function(page, key) { + if(page.searchTerms) { + index.add({ + id : key, + title : page.searchTerms.titleWords, + keywords : page.searchTerms.keywords, + members : page.searchTerms.members + }); + }; + }); + }, 500); }); - return results; + localSearchFactory.$inject = ['$http', '$timeout', '$q']; + + // 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) { + var hits = index.search(q); + var results = []; + angular.forEach(hits, function(hit) { + results.push(pagesData[hit.ref]); + }); + return $q.when(results); + }; + } + + // This version of the service builds the index in a WebWorker, + // which does not block rendering and other browser activities. + // It should only be used where the browser does support WebWorkers + function webWorkerSearchFactory($q) { + + console.log('Using WebWorker Search Index') + + var results; + + var worker = new Worker('js/search.js'); + + // The worker will send us a message when it has completed a + // search query and has the result available + worker.onmessage = function(oEvent) { + results.resolve(oEvent.data); + }; + + // The actual service is a function that takes a query string and + // returns a promise to the search results + return function(q) { + results = $q.defer(); + worker.postMessage({ q: q }); + return results.promise; + }; + } + webWorkerSearchFactory.$inject = ['$q']; + + return { + $get: window.Worker ? webWorkerSearchFactory : localSearchFactory }; -}]) +}) .directive('focused', function($timeout) { return function(scope, element, attrs) { From 93f0af2eae588683d8c67ed9df65cb2412c8eee3 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Tue, 23 Sep 2014 14:53:25 +0100 Subject: [PATCH 06/10] chore(docs): add watch task to gulp --- docs/gulpfile.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/gulpfile.js b/docs/gulpfile.js index 5dfdd82495f6..821705c5328e 100644 --- a/docs/gulpfile.js +++ b/docs/gulpfile.js @@ -17,6 +17,8 @@ var path = require('canonical-path'); var outputFolder = '../build/docs'; var bowerFolder = 'bower_components'; +var src = 'app/src/**/*.js'; +var assets = 'app/assets/**/*'; var copyComponent = function(component, pattern, sourceFolder, packageFile) { pattern = pattern || '/**/*'; @@ -40,14 +42,14 @@ gulp.task('bower', function() { }); gulp.task('build-app', function() { - gulp.src('app/src/**/*.js') + gulp.src(src) .pipe(concat('docs.js')) .pipe(gulp.dest(outputFolder + '/js/')); }); gulp.task('assets', ['bower'], function() { return merge( - gulp.src(['app/assets/**/*']).pipe(gulp.dest(outputFolder)), + gulp.src([assets]).pipe(gulp.dest(outputFolder)), copyComponent('bootstrap', '/dist/**/*'), copyComponent('open-sans-fontface'), copyComponent('lunr.js','/*.js'), @@ -77,3 +79,6 @@ gulp.task('jshint', ['doc-gen'], function() { // The default task that will be run if no task is supplied gulp.task('default', ['assets', 'doc-gen', 'build-app', 'jshint']); +gulp.task('watch', function() { + gulp.watch([src, assets], ['assets', 'build-app']); +}); From 56a4d44c40423cd0f7693dc16d057c6d19f3d05a Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Tue, 23 Sep 2014 14:53:42 +0100 Subject: [PATCH 07/10] chore(docs): improve search results layout --- docs/app/assets/css/docs.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/app/assets/css/docs.css b/docs/app/assets/css/docs.css index e441606c1821..72249876dfbc 100644 --- a/docs/app/assets/css/docs.css +++ b/docs/app/assets/css/docs.css @@ -316,10 +316,10 @@ iframe.example { } .search-results-group.col-group-api { width:30%; } -.search-results-group.col-group-guide { width:30%; } -.search-results-group.col-group-tutorial { width:25%; } +.search-results-group.col-group-guide, +.search-results-group.col-group-tutorial { width:20%; } .search-results-group.col-group-misc, -.search-results-group.col-group-error { float:right; clear:both; width:15% } +.search-results-group.col-group-error { width:15%; float: right; } .search-results-group.col-group-api .search-result { From 20f4e11b40d8beed37061f199258ab0495f54d03 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Tue, 23 Sep 2014 14:53:58 +0100 Subject: [PATCH 08/10] chore(docs): reduce the burden of pages-data to download --- docs/app/assets/js/search-worker.js | 44 +++++++++ docs/app/assets/js/search.js | 54 ----------- docs/app/src/docs.js | 33 +++---- docs/app/src/search.js | 91 ++++++++++++------- docs/config/index.js | 15 ++- docs/config/processors/pages-data.js | 72 ++++++++------- docs/config/templates/indexPage.template.html | 11 +-- 7 files changed, 162 insertions(+), 158 deletions(-) create mode 100644 docs/app/assets/js/search-worker.js delete mode 100644 docs/app/assets/js/search.js diff --git a/docs/app/assets/js/search-worker.js b/docs/app/assets/js/search-worker.js new file mode 100644 index 000000000000..6c3c96dd54b9 --- /dev/null +++ b/docs/app/assets/js/search-worker.js @@ -0,0 +1,44 @@ +"use strict"; +/* jshint browser: true */ +/* global importScripts, onmessage: true, postMessage, lunr */ + +// Load up the lunr library +importScripts('../components/lunr.js-0.4.2/lunr.min.js'); + +// Create the lunr index - the docs should be an array of object, each object containing +// the path and search terms for a page +var index = lunr(function() { + this.ref('path'); + this.field('titleWords', {boost: 50}); + this.field('members', { boost: 40}); + this.field('keywords', { boost : 20 }); +}); + +// Retrieve the searchData which contains the information about each page to be indexed +var searchData = {}; +var searchDataRequest = new XMLHttpRequest(); +searchDataRequest.onload = function() { + + // Store the pages data to be used in mapping query results back to pages + searchData = JSON.parse(this.responseText); + // Add search terms from each page to the search index + searchData.forEach(function(page) { + index.add(page); + }); + postMessage({ e: 'index-ready' }); +}; +searchDataRequest.open('GET', 'search-data.json'); +searchDataRequest.send(); + +// The worker receives a message everytime the web app wants to query the index +onmessage = function(oEvent) { + var q = oEvent.data.q; + var hits = index.search(q); + var results = []; + // Only return the array of paths to pages + hits.forEach(function(hit) { + results.push(hit.ref); + }); + // The results of the query are sent back to the web app via a new message + postMessage({ e: 'query-ready', q: q, d: results }); +}; \ No newline at end of file diff --git a/docs/app/assets/js/search.js b/docs/app/assets/js/search.js deleted file mode 100644 index 69f5eb046af0..000000000000 --- a/docs/app/assets/js/search.js +++ /dev/null @@ -1,54 +0,0 @@ -"use strict"; -/* jshint browser: true */ -/* global importScripts, onmessage: true, postMessage, lunr */ - -// Load up the lunr library -importScripts('../components/lunr.js-0.4.2/lunr.min.js'); - -// 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 }); -}); - -// Retrieve the pagesData which contains the information about each -// page to be indexed -var pagesData = {}; -var pagesDataRequest = new XMLHttpRequest(); -pagesDataRequest.onload = function() { - - // Store the pages data to be used in mapping query - // results back to pages - pagesData = JSON.parse(this.responseText); - - // Add search terms from each page to the search index - for(var path in pagesData) { - var page = pagesData[path]; - if(page.searchTerms) { - index.add({ - path : path, // the path is the unique key for the page - titleWords : page.searchTerms.titleWords, - keywords : page.searchTerms.keywords, - members : page.searchTerms.members - }); - } - } -}; -pagesDataRequest.open('GET', 'pages-data.json'); -pagesDataRequest.send(); - -// The worker receives a message everytime the web app -// wants to query the index -onmessage = function(oEvent) { - var q = oEvent.data.q; - var hits = index.search(q); - var results = []; - hits.forEach(function(hit) { - results.push(pagesData[hit.ref]); - }); - // The results of the query are sent back to the web app - // via a new message - postMessage(results); -}; \ No newline at end of file diff --git a/docs/app/src/docs.js b/docs/app/src/docs.js index e0f6b5509082..9e3af4b773a3 100644 --- a/docs/app/src/docs.js +++ b/docs/app/src/docs.js @@ -25,10 +25,18 @@ angular.module('DocsController', []) }; }; - $scope.afterPartialLoaded = function() { + $scope.$on('$includeContentLoaded', function() { var pagePath = $scope.currentPage ? $scope.currentPage.path : $location.path(); $window._gaq.push(['_trackPageview', pagePath]); - }; + }); + + $scope.$on('$includeContentError', function() { + $scope.partialPath = 'Error404.html'; + }); + + $scope.$watch(function() { return $location.path(); }, function(path) { + $scope.partialPath = 'partials' + path + '.html'; + }); /********************************** @@ -38,28 +46,11 @@ angular.module('DocsController', []) $scope.$watch(function docsPathWatch() {return $location.path(); }, function docsPathWatchAction(path) { - var currentPage = $scope.currentPage = NG_PAGES[path]; - if ( !currentPage && path.charAt(0)==='/' ) { - // Strip off leading slash - path = path.substr(1); - } - - currentPage = $scope.currentPage = NG_PAGES[path]; - if ( !currentPage && path.charAt(path.length-1) === '/' && path.length > 1 ) { - // Strip off trailing slash - path = path.substr(0, path.length-1); - } - - currentPage = $scope.currentPage = NG_PAGES[path]; - if ( !currentPage && /\/index$/.test(path) ) { - // Strip off index from the end - path = path.substr(0, path.length - 6); - } - + path = path.replace(/^\/?(.+?)\/?$/, '$1'); currentPage = $scope.currentPage = NG_PAGES[path]; if ( currentPage ) { - $scope.currentArea = currentPage && NG_NAVIGATION[currentPage.area]; + $scope.currentArea = NG_NAVIGATION[currentPage.area]; var pathParts = currentPage.path.split('/'); var breadcrumb = $scope.breadcrumb = []; var breadcrumbPath = ''; diff --git a/docs/app/src/search.js b/docs/app/src/search.js index 7352000260b0..d51f21d768de 100644 --- a/docs/app/src/search.js +++ b/docs/app/src/search.js @@ -59,89 +59,112 @@ angular.module('search', []) }; }]) -.controller('Error404SearchCtrl', ['$scope', '$location', 'docsSearch', function($scope, $location, docsSearch) { - $scope.results = docsSearch($location.path().split(/[\/\.:]/).pop()); + +.controller('Error404SearchCtrl', ['$scope', '$location', 'docsSearch', + function($scope, $location, docsSearch) { + docsSearch($location.path().split(/[\/\.:]/).pop()).then(function(results) { + $scope.results = {}; + angular.forEach(results, function(result) { + var area = $scope.results[result.area] || []; + area.push(result); + $scope.results[result.area] = area; + }); + }); }]) + .provider('docsSearch', function() { // This version of the service builds the index in the current thread, // which blocks rendering and other browser activities. // It should only be used where the browser does not support WebWorkers - function localSearchFactory($http, $timeout, $q) { + function localSearchFactory($http, $timeout, NG_PAGES) { console.log('Using Local Search Index'); // Create the lunr index var index = lunr(function() { - this.ref('id'); - this.field('title', {boost: 50}); + this.ref('path'); + this.field('titleWords', {boost: 50}); this.field('members', { boost: 40}); this.field('keywords', { boost : 20 }); }); - var pagesData = {}; - // Delay building the index by loading the data asynchronously - $http.get('js/pages-data.json').then(function(response) { - pagesData = response.data; + var indexReadyPromise = $http.get('js/search-data.json').then(function(response) { + var searchData = response.data; // Delay building the index for 500ms to allow the page to render - $timeout(function() { + return $timeout(function() { // load the page data into the index - angular.forEach(pagesData, function(page, key) { - if(page.searchTerms) { - index.add({ - id : key, - title : page.searchTerms.titleWords, - keywords : page.searchTerms.keywords, - members : page.searchTerms.members - }); - }; + angular.forEach(searchData, function(page) { + index.add(page); }); }, 500); }); - localSearchFactory.$inject = ['$http', '$timeout', '$q']; // 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) { - var hits = index.search(q); - var results = []; - angular.forEach(hits, function(hit) { - results.push(pagesData[hit.ref]); + return indexReadyPromise.then(function() { + var hits = index.search(q); + var results = []; + angular.forEach(hits, function(hit) { + results.push(NG_PAGES[hit.ref]); + }); + return results; }); - return $q.when(results); }; } + localSearchFactory.$inject = ['$http', '$timeout', 'NG_PAGES']; // This version of the service builds the index in a WebWorker, // which does not block rendering and other browser activities. // It should only be used where the browser does support WebWorkers - function webWorkerSearchFactory($q) { + function webWorkerSearchFactory($q, $rootScope, NG_PAGES) { console.log('Using WebWorker Search Index') + var searchIndex = $q.defer(); var results; - var worker = new Worker('js/search.js'); + var worker = new Worker('js/search-worker.js'); - // The worker will send us a message when it has completed a - // search query and has the result available + // The worker will send us a message in two situations: + // - when the index has been built, ready to run a query + // - when it has completed a search query and the results are available worker.onmessage = function(oEvent) { - results.resolve(oEvent.data); + $rootScope.$apply(function() { + + switch(oEvent.data.e) { + case 'index-ready': + searchIndex.resolve(); + break; + case 'query-ready': + var pages = oEvent.data.d.map(function(path) { + return NG_PAGES[path]; + }); + results.resolve(pages); + break; + } + }); }; // The actual service is a function that takes a query string and // returns a promise to the search results return function(q) { - results = $q.defer(); - worker.postMessage({ q: q }); - return results.promise; + + // We only run the query once the index is ready + return searchIndex.promise.then(function() { + + results = $q.defer(); + worker.postMessage({ q: q }); + return results.promise; + }); }; } - webWorkerSearchFactory.$inject = ['$q']; + webWorkerSearchFactory.$inject = ['$q', '$rootScope', 'NG_PAGES']; return { $get: window.Worker ? webWorkerSearchFactory : localSearchFactory diff --git a/docs/config/index.js b/docs/config/index.js index 0f71cb54c1ce..6252788e8f3c 100644 --- a/docs/config/index.js +++ b/docs/config/index.js @@ -92,10 +92,7 @@ module.exports = new Package('angularjs', [ } return docPath; }, - getOutputPath: function(doc) { - return 'partials/' + doc.path + - ( doc.fileInfo.baseName === 'index' ? '/index.html' : '.html'); - } + outputPathTemplate: 'partials/${path}.html' }); computePathsProcessor.pathTemplates.push({ @@ -110,6 +107,16 @@ module.exports = new Package('angularjs', [ outputPathTemplate: '${id}.html' }); + computePathsProcessor.pathTemplates.push({ + docTypes: ['module' ], + pathTemplate: '${area}/${name}', + outputPathTemplate: 'partials/${area}/${name}.html' + }); + computePathsProcessor.pathTemplates.push({ + docTypes: ['componentGroup' ], + pathTemplate: '${area}/${moduleName}/${groupType}', + outputPathTemplate: 'partials/${area}/${moduleName}/${groupType}.html' + }); computeIdsProcessor.idTemplates.push({ docTypes: ['overview', 'tutorial', 'e2e-test', 'indexPage'], diff --git a/docs/config/processors/pages-data.js b/docs/config/processors/pages-data.js index 069fef3b0928..efa79a5de4f2 100644 --- a/docs/config/processors/pages-data.js +++ b/docs/config/processors/pages-data.js @@ -147,24 +147,18 @@ module.exports = function generatePagesDataProcessor(log) { }; return { - $runAfter: ['paths-computed'], + $runAfter: ['paths-computed', 'generateKeywordsProcessor'], $runBefore: ['rendering-docs'], $process: function(docs) { - _(docs) - .filter(function(doc) { return doc.area === 'api' && doc.docType === 'module'; }) - .forEach(function(doc) { if ( !doc.path ) { - log.warn('Missing path property for ', doc.id); - }}) - .map(function(doc) { return _.pick(doc, ['id', 'module', 'docType', 'area']); }) - .tap(function(docs) { - log.debug(docs); + // We are only interested in docs that are in an area + var pages = _.filter(docs, function(doc) { + return doc.area; }); - - // We are only interested in docs that are in an area and are not landing pages - var navPages = _.filter(docs, function(page) { - return page.area && page.docType != 'componentGroup'; + // We are only interested in pages that are not landing pages + var navPages = _.filter(pages, function(page) { + return page.docType != 'componentGroup'; }); // Generate an object collection of pages that is grouped by area e.g. @@ -198,13 +192,37 @@ module.exports = function generatePagesDataProcessor(log) { area.navGroups = navGroupMapper(pages, area); }); + docs.push({ + docType: 'nav-data', + id: 'nav-data', + template: 'nav-data.template.js', + outputPath: 'js/nav-data.js', + areas: areas + }); + + + + var searchData = _(pages) + .filter(function(page) { + return page.searchTerms; + }) + .map(function(page) { + return _.extend({ path: page.path }, page.searchTerms); + }) + .value(); + + docs.push({ + docType: 'json-doc', + id: 'search-data-json', + template: 'json-doc.template.json', + outputPath: 'js/search-data.json', + data: searchData + }); + // Extract a list of basic page information for mapping paths to partials and for client side searching - var pages = _(docs) + var pageData = _(docs) .map(function(doc) { - var page = _.pick(doc, [ - 'docType', 'id', 'name', 'area', 'outputPath', 'path', 'searchTerms' - ]); - return page; + return _.pick(doc, ['name', 'area', 'path']); }) .indexBy('path') .value(); @@ -214,23 +232,7 @@ module.exports = function generatePagesDataProcessor(log) { id: 'pages-data', template: 'pages-data.template.js', outputPath: 'js/pages-data.js', - pages: pages - }); - - docs.push({ - docType: 'json-doc', - id: 'pages-data-json', - template: 'json-doc.template.json', - outputPath: 'js/pages-data.json', - data: pages - }); - - docs.push({ - docType: 'nav-data', - id: 'nav-data', - template: 'nav-data.template.js', - outputPath: 'js/nav-data.js', - areas: areas + pages: pageData }); } }; diff --git a/docs/config/templates/indexPage.template.html b/docs/config/templates/indexPage.template.html index 42f79ad4167e..f18dc0e94b4e 100644 --- a/docs/config/templates/indexPage.template.html +++ b/docs/config/templates/indexPage.template.html @@ -56,15 +56,6 @@ } })(); - - // force page reload when new update is available - window.applicationCache && window.applicationCache.addEventListener('updateready', function(e) { - if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { - window.applicationCache.swapCache(); - window.location.reload(); - } - }, false); - // GA asynchronous tracker var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-8594346-3']); @@ -219,7 +210,7 @@

{{ key }}

Loading...
-
+
From 7f50c849007e1d5838b470d965004badf0a1c615 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Tue, 23 Sep 2014 18:28:51 +0100 Subject: [PATCH 09/10] chore(docs): fix corner-case path handling --- docs/app/e2e/app.scenario.js | 11 +++++++---- docs/app/src/docs.js | 23 ++++++----------------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/docs/app/e2e/app.scenario.js b/docs/app/e2e/app.scenario.js index ac456428119f..68bbbf9cfb7d 100644 --- a/docs/app/e2e/app.scenario.js +++ b/docs/app/e2e/app.scenario.js @@ -77,10 +77,13 @@ describe('docs.angularjs.org', function () { }); - it("should display an error if the page does not exist", function() { - browser.get('index-debug.html#!/api/does/not/exist'); - expect(element(by.css('h1')).getText()).toBe('Oops!'); - }); }); }); + +describe('Error Handling', function() { + it("should display an error if the page does not exist", function() { + browser.get('index-debug.html#!/api/does/not/exist'); + expect(element(by.css('h1')).getText()).toBe('Oops!'); + }); +}); \ No newline at end of file diff --git a/docs/app/src/docs.js b/docs/app/src/docs.js index 9e3af4b773a3..8ebdc352eccc 100644 --- a/docs/app/src/docs.js +++ b/docs/app/src/docs.js @@ -6,18 +6,10 @@ angular.module('DocsController', []) function($scope, $rootScope, $location, $window, $cookies, openPlunkr, NG_PAGES, NG_NAVIGATION, NG_VERSION) { - $scope.openPlunkr = openPlunkr; $scope.docsVersion = NG_VERSION.isSnapshot ? 'snapshot' : NG_VERSION.version; - var INDEX_PATH = /^(\/|\/index[^\.]*.html)$/; - - - /********************************** - Publish methods - ***********************************/ - $scope.navClass = function(navItem) { return { active: navItem.href && this.currentPage && this.currentPage.path, @@ -25,6 +17,8 @@ angular.module('DocsController', []) }; }; + + $scope.$on('$includeContentLoaded', function() { var pagePath = $scope.currentPage ? $scope.currentPage.path : $location.path(); $window._gaq.push(['_trackPageview', pagePath]); @@ -34,19 +28,13 @@ angular.module('DocsController', []) $scope.partialPath = 'Error404.html'; }); - $scope.$watch(function() { return $location.path(); }, function(path) { - $scope.partialPath = 'partials' + path + '.html'; - }); - - /********************************** - Watches - ***********************************/ + $scope.$watch(function docsPathWatch() {return $location.path(); }, function docsPathWatchAction(path) { + path = path.replace(/^\/?(.+?)(\/index)?\/?$/, '$1'); - $scope.$watch(function docsPathWatch() {return $location.path(); }, function docsPathWatchAction(path) { + $scope.partialPath = 'partials/' + path + '.html'; - path = path.replace(/^\/?(.+?)\/?$/, '$1'); currentPage = $scope.currentPage = NG_PAGES[path]; if ( currentPage ) { @@ -74,6 +62,7 @@ angular.module('DocsController', []) $scope.loading = 0; + var INDEX_PATH = /^(\/|\/index[^\.]*.html)$/; if (!$location.path() || INDEX_PATH.test($location.path())) { $location.path('/api').replace(); } From f156c32a7abfe22f2c9f2105f4cbfd913a2ee4f6 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Tue, 23 Sep 2014 18:43:08 +0100 Subject: [PATCH 10/10] chore(docs): Google Analytics is now triggered by ngInclude events not handlers --- docs/app/test/docsSpec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/app/test/docsSpec.js b/docs/app/test/docsSpec.js index bf635bf87927..477e6ddbca75 100644 --- a/docs/app/test/docsSpec.js +++ b/docs/app/test/docsSpec.js @@ -19,7 +19,7 @@ describe("DocsController", function() { it("should update the Google Analytics with currentPage path if currentPage exists", inject(function($window) { $window._gaq = []; $scope.currentPage = { path: 'a/b/c' }; - $scope.afterPartialLoaded(); + $scope.$broadcast('$includeContentLoaded'); expect($window._gaq.pop()).toEqual(['_trackPageview', 'a/b/c']); })); @@ -27,7 +27,7 @@ describe("DocsController", function() { it("should update the Google Analytics with $location.path if currentPage is missing", inject(function($window, $location) { $window._gaq = []; spyOn($location, 'path').andReturn('x/y/z'); - $scope.afterPartialLoaded(); + $scope.$broadcast('$includeContentLoaded'); expect($window._gaq.pop()).toEqual(['_trackPageview', 'x/y/z']); })); });