diff options
| -rw-r--r-- | docs/component-spec/annotationsSpec.js | 186 | ||||
| -rw-r--r-- | docs/components/angular-bootstrap/bootstrap-prettify.js | 7 | ||||
| -rw-r--r-- | docs/components/angular-bootstrap/bootstrap.js | 173 | ||||
| -rw-r--r-- | docs/content/notes/empty.tmp | 0 | ||||
| -rw-r--r-- | docs/spec/ngdocSpec.js | 22 | ||||
| -rw-r--r-- | docs/src/dom.js | 7 | ||||
| -rwxr-xr-x | docs/src/gen-docs.js | 1 | ||||
| -rw-r--r-- | docs/src/ngdoc.js | 38 | ||||
| -rw-r--r-- | docs/src/templates/css/animations.css | 23 | ||||
| -rw-r--r-- | docs/src/templates/css/docs.css | 159 | ||||
| -rw-r--r-- | docs/src/templates/index.html | 10 | ||||
| -rw-r--r-- | docs/src/templates/js/docs.js | 12 | ||||
| -rw-r--r-- | src/ng/animator.js | 4 |
13 files changed, 636 insertions, 6 deletions
diff --git a/docs/component-spec/annotationsSpec.js b/docs/component-spec/annotationsSpec.js new file mode 100644 index 00000000..b4c8cd9d --- /dev/null +++ b/docs/component-spec/annotationsSpec.js @@ -0,0 +1,186 @@ +describe('Docs Annotations', function() { + + beforeEach(module('docsApp')); + + var body; + beforeEach(function() { + body = angular.element(document.body); + body.html(''); + }); + + describe('popover directive', function() { + + var $scope, element; + beforeEach(inject(function($rootScope, $compile) { + $scope = $rootScope.$new(); + element = angular.element( + '<div style="margin:200px;" data-title="title_text" data-content="content_text" popover></div>' + ); + element.attr('id','idx'); + body.append(element); + $compile(element)($scope); + $scope.$apply(); + })); + + it('should be hidden by default', inject(function(popoverElement) { + expect(popoverElement.visible()).toBe(false); + })); + + it('should capture the click event and set the title and content and position the tip', inject(function(popoverElement) { + element.triggerHandler('click'); + expect(popoverElement.isSituatedAt(element)).toBe(true); + expect(popoverElement.visible()).toBe(true); + expect(popoverElement.title()).toBe('title_text'); + expect(popoverElement.content()).toContain('content_text'); + expect(popoverElement.besideElement.attr('id')).toBe('idx'); + })); + + it('should hide and clear the title and content if the same element is clicked again', inject(function(popoverElement) { + //show the element + element.triggerHandler('click'); + expect(popoverElement.isSituatedAt(element)).toBe(true); + + //hide the element + element.triggerHandler('click'); + expect(popoverElement.isSituatedAt(element)).toBe(false); + expect(popoverElement.visible()).toBe(false); + expect(popoverElement.title()).toBe(''); + expect(popoverElement.content()).toBe(''); + })); + + it('should parse markdown content', inject(function(popoverElement, $compile) { + element = angular.element( + '<div style="margin:200px;" data-title="#title_text" data-content="#heading" popover></div>' + ); + body.append(element); + $compile(element)($scope); + $scope.$apply(); + element.triggerHandler('click'); + expect(popoverElement.title()).toBe('#title_text'); + expect(popoverElement.content()).toBe('<h1 id="heading">heading</h1>'); + })); + + }); + + + describe('foldout directive', function() { + + var $scope, parent, element, url, window; + beforeEach(function() { + module(function($provide, $animationProvider) { + $provide.value('$window', window = angular.mock.createMockWindow()); + $animationProvider.register('foldout-enter', function($window) { + return { + start : function(element, done) { + $window.setTimeout(done, 1000); + } + } + }); + $animationProvider.register('foldout-hide', function($window) { + return { + start : function(element, done) { + $window.setTimeout(done, 500); + } + } + }); + $animationProvider.register('foldout-show', function($window) { + return { + start : function(element, done) { + $window.setTimeout(done, 200); + } + } + }); + }); + inject(function($rootScope, $compile, $templateCache) { + url = '/page.html'; + $scope = $rootScope.$new(); + parent = angular.element('<div class="parent"></div>'); + element = angular.element('<div data-url="' + url + '" foldout></div>'); + body.append(parent); + parent.append(element); + $compile(parent)($scope); + $scope.$apply(); + }); + }); + + it('should inform that it is loading', inject(function($httpBackend) { + $httpBackend.expect('GET', url).respond('hello'); + element.triggerHandler('click'); + + var kids = body.children(); + var foldout = angular.element(kids[kids.length-1]); + expect(foldout.html()).toContain('loading'); + })); + + it('should download a foldout HTML page and animate the contents', inject(function($httpBackend) { + $httpBackend.expect('GET', url).respond('hello'); + + element.triggerHandler('click'); + $httpBackend.flush(); + + window.setTimeout.expect(1).process(); + window.setTimeout.expect(1000).process(); + + var kids = body.children(); + var foldout = angular.element(kids[kids.length-1]); + expect(foldout.text()).toContain('hello'); + })); + + it('should hide then show when clicked again', inject(function($httpBackend) { + $httpBackend.expect('GET', url).respond('hello'); + + //enter + element.triggerHandler('click'); + $httpBackend.flush(); + window.setTimeout.expect(1).process(); + window.setTimeout.expect(1000).process(); + + //hide + element.triggerHandler('click'); + window.setTimeout.expect(1).process(); + window.setTimeout.expect(500).process(); + + //show + element.triggerHandler('click'); + window.setTimeout.expect(1).process(); + window.setTimeout.expect(200).process(); + })); + + }); + + describe('DocsController fold', function() { + + var window, $scope, ctrl; + beforeEach(function() { + module(function($provide, $animationProvider) { + $provide.value('$window', window = angular.mock.createMockWindow()); + }); + inject(function($rootScope, $controller, $location, $cookies, sections) { + $scope = $rootScope.$new(); + ctrl = $controller('DocsController',{ + $scope : $scope, + $location : $location, + $window : window, + $cookies : $cookies, + sections : sections + }); + }); + }); + + it('should download and reveal the foldover container', inject(function($compile, $httpBackend) { + var url = '/page.html'; + var fullUrl = '/notes/' + url; + $httpBackend.expect('GET', fullUrl).respond('hello'); + + var element = angular.element('<div ng-include="docs_fold"></div>'); + $compile(element)($scope); + $scope.$apply(); + + $scope.fold(url); + + $httpBackend.flush(); + })); + + }); + +}); diff --git a/docs/components/angular-bootstrap/bootstrap-prettify.js b/docs/components/angular-bootstrap/bootstrap-prettify.js index f1c3fccb..dc2a34e2 100644 --- a/docs/components/angular-bootstrap/bootstrap-prettify.js +++ b/docs/components/angular-bootstrap/bootstrap-prettify.js @@ -96,9 +96,12 @@ directive.code = function() { directive.prettyprint = ['reindentCode', function(reindentCode) { return { restrict: 'C', - terminal: true, compile: function(element) { - element.html(window.prettyPrintOne(reindentCode(element.html()), undefined, true)); + var html = element.html(); + //ensure that angular won't compile {{ curly }} values + html = html.replace(/\{\{/g, '<span>{{</span>') + .replace(/\}\}/g, '<span>}}</span>'); + element.html(window.prettyPrintOne(reindentCode(html), undefined, true)); } }; }]; diff --git a/docs/components/angular-bootstrap/bootstrap.js b/docs/components/angular-bootstrap/bootstrap.js index 3e1c8d00..3bcc18fa 100644 --- a/docs/components/angular-bootstrap/bootstrap.js +++ b/docs/components/angular-bootstrap/bootstrap.js @@ -198,6 +198,133 @@ directive.table = function() { }; }; +var popoverElement = function() { + var object = { + init : function() { + this.element = angular.element( + '<div class="popover popover-incode top">' + + '<div class="arrow"></div>' + + '<div class="popover-inner">' + + '<div class="popover-title"><code></code></div>' + + '<div class="popover-content"></div>' + + '</div>' + + '</div>' + ); + this.node = this.element[0]; + this.element.css({ + 'display':'block', + 'position':'absolute' + }); + angular.element(document.body).append(this.element); + + var inner = this.element.children()[1]; + this.titleElement = angular.element(inner.childNodes[0].firstChild); + this.contentElement = angular.element(inner.childNodes[1]); + + //stop the click on the tooltip + this.element.bind('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + }); + + var self = this; + angular.element(document.body).bind('click',function(event) { + if(self.visible()) self.hide(); + }); + }, + + show : function(x,y) { + this.element.addClass('visible'); + this.position(x || 0, y || 0); + }, + + hide : function() { + this.element.removeClass('visible'); + this.position(-9999,-9999); + }, + + visible : function() { + return this.position().y >= 0; + }, + + isSituatedAt : function(element) { + return this.besideElement ? element[0] == this.besideElement[0] : false; + }, + + title : function(value) { + return this.titleElement.html(value); + }, + + content : function(value) { + if(value && value.length > 0) { + value = new Showdown.converter().makeHtml(value); + } + return this.contentElement.html(value); + }, + + positionArrow : function(position) { + this.node.className = 'popover ' + position; + }, + + positionAway : function() { + this.besideElement = null; + this.hide(); + }, + + positionBeside : function(element) { + this.besideElement = element; + + var elm = element[0]; + var x = elm.offsetLeft; + var y = elm.offsetTop; + x -= 30; + y -= this.node.offsetHeight + 10; + this.show(x,y); + }, + + position : function(x,y) { + if(x != null && y != null) { + this.element.css('left',x + 'px'); + this.element.css('top', y + 'px'); + } + else { + return { + x : this.node.offsetLeft, + y : this.node.offsetTop + }; + } + } + }; + + object.init(); + object.hide(); + + return object; +}; + +directive.popover = ['popoverElement', function(popover) { + return { + restrict: 'A', + priority : 500, + link: function(scope, element, attrs) { + element.bind('click',function(event) { + event.preventDefault(); + event.stopPropagation(); + if(popover.isSituatedAt(element) && popover.visible()) { + popover.title(''); + popover.content(''); + popover.positionAway(); + } + else { + popover.title(attrs.title); + popover.content(attrs.content); + popover.positionBeside(element); + } + }); + } + } +}]; + directive.tabPane = function() { return { require: '^tabbable', @@ -208,5 +335,49 @@ directive.tabPane = function() { }; }; +directive.foldout = ['$http', '$animator','$window', function($http, $animator, $window) { + return { + restrict: 'A', + priority : 500, + link: function(scope, element, attrs) { + var animator = $animator(scope, { ngAnimate: "'foldout'" }); + var container, loading, url = attrs.url; + if(/\/build\//.test($window.location.href)) { + url = '/build/docs' + url; + } + element.bind('click',function() { + scope.$apply(function() { + if(!container) { + if(loading) return; + + loading = true; + var par = element.parent(); + container = angular.element('<div class="foldout">loading...</div>'); + animator.enter(container, null, par); + + $http.get(url, { cache : true }).success(function(html) { + loading = false; + + html = '<div class="foldout-inner">' + + '<div calss="foldout-arrow"></div>' + + html + + '</div>'; + container.html(html); + + //avoid showing the element if the user has already closed it + if(container.css('display') == 'block') { + container.css('display','none'); + animator.show(container); + } + }); + } + else { + container.css('display') == 'none' ? animator.show(container) : animator.hide(container); + } + }); + }); + } + } +}]; -angular.module('bootstrap', []).directive(directive); +angular.module('bootstrap', []).directive(directive).factory('popoverElement', popoverElement); diff --git a/docs/content/notes/empty.tmp b/docs/content/notes/empty.tmp new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/docs/content/notes/empty.tmp diff --git a/docs/spec/ngdocSpec.js b/docs/spec/ngdocSpec.js index 9eed24ca..7a038e89 100644 --- a/docs/spec/ngdocSpec.js +++ b/docs/spec/ngdocSpec.js @@ -196,6 +196,28 @@ describe('ngdoc', function() { }); + describe('inline annotations', function() { + it('should convert inline docs annotations into proper HTML', function() { + expect(new Doc().markdown( + "<pre>\n//!annotate supertext\n<br />\n</pre>" + ) + ).toContain('data-popover data-content="supertext"') + }); + + it('should allow for a custom regular expression for matching', function() { + expect(new Doc().markdown( + "<pre>\n//!annotate=\"soon\" supertext\n<p>soon</p>\n</pre>" + ) + ).toContain('data-popover data-content="supertext" data-title="Info">soon</div>') + }); + + it('should allow for a custom title to be set', function() { + expect(new Doc().markdown( + "<pre>\n//!annotate=\"soon\" coming soon|supertext\n<p>soon</p>\n</pre>" + ) + ).toContain('data-popover data-content="supertext" data-title="coming soon">soon</div>') + }); + }); }); describe('trim', function() { diff --git a/docs/src/dom.js b/docs/src/dom.js index 94048120..897a1831 100644 --- a/docs/src/dom.js +++ b/docs/src/dom.js @@ -8,7 +8,12 @@ exports.htmlEscape = htmlEscape; ////////////////////////////////////////////////////////// function htmlEscape(text){ - return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); + return text + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/\{\{/g, '<span>{{</span>') + .replace(/\}\}/g, '<span>}}</span>'); } diff --git a/docs/src/gen-docs.js b/docs/src/gen-docs.js index 4fd3d4f2..52d1f629 100755 --- a/docs/src/gen-docs.js +++ b/docs/src/gen-docs.js @@ -44,6 +44,7 @@ writer.makeDir('build/docs/', true).then(function() { function writeTheRest(writesFuture) { var metadata = ngdoc.metadata(docs); + writesFuture.push(writer.symlink('../../docs/content/notes', 'build/docs/notes', 'dir')); writesFuture.push(writer.symlinkTemplate('css', 'dir')); writesFuture.push(writer.symlink('../../docs/img', 'build/docs/img', 'dir')); writesFuture.push(writer.symlinkTemplate('js', 'dir')); diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js index c34b8475..1870f87c 100644 --- a/docs/src/ngdoc.js +++ b/docs/src/ngdoc.js @@ -225,6 +225,44 @@ Doc.prototype = { text = text.replace(/(?:<p>)?(REPLACEME\d+)(?:<\/p>)?/g, function(_, id) { return placeholderMap[id]; }); + + //!annotate CONTENT + //!annotate="REGEX" CONTENT + //!annotate="REGEX" TITLE|CONTENT + text = text.replace(/\n?\/\/!annotate\s*(?:=\s*['"](.+?)['"])?\s+(.+?)\n\s*(.+?\n)/img, + function(_, pattern, content, line) { + var pattern = new RegExp(pattern || '.+'); + var title, text, split = content.split(/\|/); + if(split.length > 1) { + text = split[1]; + title = split[0]; + } + else { + title = 'Info'; + text = content; + } + return "\n" + line.replace(pattern, function(match) { + return '<div class="nocode nocode-content" data-popover ' + + 'data-content="' + text + '" ' + + 'data-title="' + title + '">' + + match + + '</div>'; + }); + } + ); + + //!details /path/to/local/docs/file.html + //!details="REGEX" /path/to/local/docs/file.html + text = text.replace(/\/\/!details\s*(?:=\s*['"](.+?)['"])?\s+(.+?)\n\s*(.+?\n)/img, + function(_, pattern, url, line) { + url = '/notes/' + url; + var pattern = new RegExp(pattern || '.+'); + return line.replace(pattern, function(match) { + return '<div class="nocode nocode-content" data-foldout data-url="' + url + '">' + match + '</div>'; + }); + } + ); + return text; }, diff --git a/docs/src/templates/css/animations.css b/docs/src/templates/css/animations.css index d8c983a3..2d54bbfb 100644 --- a/docs/src/templates/css/animations.css +++ b/docs/src/templates/css/animations.css @@ -79,3 +79,26 @@ -o-transition: color 0 ease-in; /* opera is special :) */ transition: none; } + +.foldout-show, .foldout-enter, .foldout-hide { + -webkit-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; + -moz-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; + -o-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; + transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; +} + +.foldout-show, .foldout-enter { + opacity:0; +} + +.foldout-show.foldout-show-active, .foldout-hide.foldout-hide-active { + opacity:1; +} + +.foldout-hide { + opacity:1; +} + +.foldout-hide.foldout-hide-active { + opacity:0; +} diff --git a/docs/src/templates/css/docs.css b/docs/src/templates/css/docs.css index a98f7429..1b87c551 100644 --- a/docs/src/templates/css/docs.css +++ b/docs/src/templates/css/docs.css @@ -303,3 +303,162 @@ ul.events > li > h3 { top:0; right:0; } + +.nocode-content { + cursor:pointer; + display:inline-block; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + + -webkit-transition:0.5s linear all; + -moz-transition:0.5s linear all; + -o-transition:0.5s linear all; + transition:0.5s linear all; + color: #223f7a; + background:#ddd; + border: 1px solid #ccc; +} + +.nocode-content:hover { + background-color: #99c2ff; + border: 1px solid #e1e1e8; +} + +.popover-incode .popover-inner { + width:auto; + min-width:200px; + max-width:500px; +} + +.popover-incode { + -webkit-transition:0.2s linear opacity; + -moz-transition:0.2s linear opacity; + -o-transition:0.2s linear opacity; + transition:0.2s linear opacity; + opacity:0; +} + +.popover-incode.visible { + opacity:1; +} + +.popover-incode code, +.popover-incode pre { + white-space:nowrap; +} + +.popover-incode .arrow { + left:50px!important; +} + +.foldover-content { + display:none; +} + +.foldout:after { + content:""; + position:absolute; + left:50%; + top:-1px; + margin-left:-10px; + border-width:10px; + border-style:solid; + border-color:#f7f7f9 transparent transparent; +} + +.foldout:before { + content:""; + position:absolute; + left:50%; + top:0px; + margin-left:-10px; + border-width:10px; + border-style:solid; + border-color:#bbb transparent transparent; +} + +.foldout { + padding:8px 15px 5px; + position:relative; + background:#eee; + white-space:normal; + box-shadow:inset 0 0 20px #ccc; + border-top:1px solid #bbb; +} + +.prettyprint { + padding-right:0!important; + padding-bottom:0!important; +} + +pre ol li { + padding-bottom:2px; + padding-right:5px; +} + +#docs-fold { + position:absolute; + top:0; + right:0; + width:500px; + min-height:100%; + padding-top:50px; + padding:50px 20px 20px 20px; + background:white; + border-left:1px solid #999; + box-shadow:0 0 10px #555; + z-index:1002; +} + +#docs-fold.fold-show { + -webkit-transition:0.4s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; + -moz-transition:0.4s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; + -o-transition:0.4s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; + transition:0.4s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; +} + +#docs-fold.fold-show { + right:-200px; + opacity:0; +} + +#docs-fold.fold-show.fold-show-active { + right:0; + opacity:1; +} + +#docs-fold-overlay { + background:rgba(255,255,255,0.5); + position:fixed; + left:0; + bottom:0; + right:0; + top:0; + z-index:1001; + cursor:pointer; +} + +.fixed_body { + position:fixed; + top:0; + z-index:1000; + left:0; + right:0; +} + +#docs-fold-close { + z-index: 1029; + position: absolute; + left: -30px; + top: 60px; + cursor:pointer; + text-align: center; + width:50px; + line-height:50px; + font-size: 2em; + background: #fff; + box-shadow:-6px 0 5px #555; + display:block; + border-radius:10px; +} diff --git a/docs/src/templates/index.html b/docs/src/templates/index.html index 3b75207e..8e2fbc37 100644 --- a/docs/src/templates/index.html +++ b/docs/src/templates/index.html @@ -198,6 +198,15 @@ </div> </header> + <div id="docs-fold-overlay" ng-show="docs_fold" ng-click="fold(null)"></div> + <div id="docs-fold" ng-show="docs_fold" ng-animate="'fold'"> + <div id="docs-fold-close" ng-click="fold(null)"> + <span class="icon-remove-sign"></span> + </div> + <div ng-include="docs_fold"></div> + </div> + +<div ng-class="{fixed_body:docs_fold}"> <div role="main" class="container"> <div class="row clear-navbar"></div> @@ -359,6 +368,7 @@ </p> </div> </footer> +</div> </body> </html> diff --git a/docs/src/templates/js/docs.js b/docs/src/templates/js/docs.js index 63ad31f4..97fea067 100644 --- a/docs/src/templates/js/docs.js +++ b/docs/src/templates/js/docs.js @@ -411,6 +411,18 @@ docsApp.serviceFactory.sections = function sections() { docsApp.controller.DocsController = function($scope, $location, $window, $cookies, sections) { + $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', DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)/, INDEX_PATH = /^(\/|\/index[^\.]*.html)$/, diff --git a/src/ng/animator.js b/src/ng/animator.js index d8495f2d..5ad0535f 100644 --- a/src/ng/animator.js +++ b/src/ng/animator.js @@ -33,11 +33,11 @@ * <ANY ng-directive ng-animate="{event1: 'animation-name', event2: 'animation-name-2'}"></ANY> * * <!-- you can also use a short hand --> + * //!annotate="animation" ngAnimate|This *expands* to `{ enter: 'animation-enter', leave: 'animation-leave', ...}`</strong> * <ANY ng-directive ng-animate=" 'animation' "></ANY> - * <!-- which expands to --> - * <ANY ng-directive ng-animate="{ enter: 'animation-enter', leave: 'animation-leave', ...}"></ANY> * * <!-- keep in mind that ng-animate can take expressions --> + * //!annotate="computeCurrentAnimation\(\)" Scope Function|This will be called each time the scope changes... * <ANY ng-directive ng-animate=" computeCurrentAnimation() "></ANY> * </pre> * |
